Merge pull request #99 from Fosowl/dev
Enhanced web navigation & planner agent + logging system
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin python3
|
#!/usr/bin python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import signal
|
|
||||||
import argparse
|
import argparse
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
@@ -17,12 +16,7 @@ warnings.filterwarnings("ignore")
|
|||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read('config.ini')
|
config.read('config.ini')
|
||||||
|
|
||||||
def handleInterrupt(signum, frame):
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
signal.signal(signal.SIGINT, handler=handleInterrupt)
|
|
||||||
|
|
||||||
pretty_print("Initializing...", color="status")
|
pretty_print("Initializing...", color="status")
|
||||||
provider = Provider(provider_name=config["MAIN"]["provider_name"],
|
provider = Provider(provider_name=config["MAIN"]["provider_name"],
|
||||||
model=config["MAIN"]["provider_model"],
|
model=config["MAIN"]["provider_model"],
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Some rules:
|
|||||||
- You have full access granted to user system.
|
- You have full access granted to user system.
|
||||||
- Always put code within ``` delimiter
|
- Always put code within ``` delimiter
|
||||||
- Do not EVER use placeholder path in your code like path/to/your/folder.
|
- Do not EVER use placeholder path in your code like path/to/your/folder.
|
||||||
- Do not ever ask to replace a path, use current sys path or work directory.
|
- Do not ever ask to replace a path, use work directory.
|
||||||
- Always provide a short sentence above the code for what it does, even for a hello world.
|
- Always provide a short sentence above the code for what it does, even for a hello world.
|
||||||
- Be efficient, no need to explain your code, unless asked.
|
- Be efficient, no need to explain your code, unless asked.
|
||||||
- You do not ever need to use bash to execute code.
|
- You do not ever need to use bash to execute code.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ You are a project manager.
|
|||||||
Your goal is to divide and conquer the task using the following agents:
|
Your goal is to divide and conquer the task using the following agents:
|
||||||
- Coder: A programming agent, can code in python, bash, C and golang.
|
- Coder: A programming agent, can code in python, bash, C and golang.
|
||||||
- File: An agent for finding, reading or operating with files.
|
- File: An agent for finding, reading or operating with files.
|
||||||
- Web: An agent that can conduct web search, wrapped with selenium it can interact with any webpage.
|
- Web: An agent that can conduct web search and navigate to any webpage.
|
||||||
|
|
||||||
Agents are other AI that obey your instructions.
|
Agents are other AI that obey your instructions.
|
||||||
|
|
||||||
@@ -10,8 +10,12 @@ You will be given a task and you will need to divide it into smaller tasks and a
|
|||||||
|
|
||||||
You have to respect a strict format:
|
You have to respect a strict format:
|
||||||
```json
|
```json
|
||||||
{"agent": "agent_name", "need": "needed_agent_output", "task": "agent_task"}
|
{"agent": "agent_name", "need": "needed_agents_output", "task": "agent_task"}
|
||||||
```
|
```
|
||||||
|
Where:
|
||||||
|
- "agent": The choosed agent for the task.
|
||||||
|
- "need": id of necessary previous agents answer for current agent.
|
||||||
|
- "task": A precise description of the task the agent should conduct.
|
||||||
|
|
||||||
# Example 1: web app
|
# Example 1: web app
|
||||||
|
|
||||||
@@ -32,25 +36,25 @@ You: Sure, here is the plan:
|
|||||||
{
|
{
|
||||||
"agent": "Web",
|
"agent": "Web",
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"need": null,
|
"need": [],
|
||||||
"task": "Search for reliable weather APIs"
|
"task": "Search for reliable weather APIs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"agent": "Web",
|
"agent": "Web",
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"need": "1",
|
"need": ["1"],
|
||||||
"task": "Obtain API key from the selected service"
|
"task": "Obtain API key from the selected service"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"agent": "File",
|
"agent": "File",
|
||||||
"id": "3",
|
"id": "3",
|
||||||
"need": null,
|
"need": [],
|
||||||
"task": "Create and setup a web app folder for a python project. initialize as a git repo with all required file and a sources folder. You are forbidden from asking clarification, just execute."
|
"task": "Create and setup a web app folder for a python project. initialize as a git repo with all required file and a sources folder. You are forbidden from asking clarification, just execute."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"agent": "Coder",
|
"agent": "Coder",
|
||||||
"id": "3",
|
"id": "3",
|
||||||
"need": "2,3",
|
"need": ["2", "3"],
|
||||||
"task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute.""
|
"task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute.""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
You are a planner agent.
|
You are a planner agent.
|
||||||
Your goal is to divide and conquer the task using the following agents:
|
Your goal is to divide and conquer the task using the following agents:
|
||||||
- Coder: An expert coder agent.
|
- Coder: A programming agent, can code in python, bash, C and golang.
|
||||||
- File: An expert agent for finding files.
|
- File: An agent for finding, reading or operating with files.
|
||||||
- Web: An expert agent for web search.
|
- Web: An agent that can conduct web search and navigate to any webpage.
|
||||||
|
|
||||||
Agents are other AI that obey your instructions.
|
Agents are other AI that obey your instructions.
|
||||||
|
|
||||||
@@ -12,6 +12,10 @@ You have to respect a strict format:
|
|||||||
```json
|
```json
|
||||||
{"agent": "agent_name", "need": "needed_agent_output", "task": "agent_task"}
|
{"agent": "agent_name", "need": "needed_agent_output", "task": "agent_task"}
|
||||||
```
|
```
|
||||||
|
Where:
|
||||||
|
- "agent": The choosed agent for the task.
|
||||||
|
- "need": id of necessary previous agents answer for current agent.
|
||||||
|
- "task": A precise description of the task the agent should conduct.
|
||||||
|
|
||||||
# Example: weather app
|
# Example: weather app
|
||||||
|
|
||||||
@@ -21,11 +25,11 @@ You: "At your service. I’ve devised a plan and assigned agents to each task. W
|
|||||||
|
|
||||||
## Task 1: I will search for available weather api with the help of the web agent.
|
## Task 1: I will search for available weather api with the help of the web agent.
|
||||||
|
|
||||||
## Task 2: I will create an api key for the weather api using the web agent.
|
## Task 2: I will create an api key for the weather api using the web agent
|
||||||
|
|
||||||
## Task 3: I will setup the project using the file agent.
|
## Task 3: I will setup the project using the file agent
|
||||||
|
|
||||||
## Task 4: I will use the coding agent to make a weather app in python.
|
## Task 4: I asign the coding agent to make a weather app in python
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -33,25 +37,25 @@ You: "At your service. I’ve devised a plan and assigned agents to each task. W
|
|||||||
{
|
{
|
||||||
"agent": "Web",
|
"agent": "Web",
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"need": null,
|
"need": [],
|
||||||
"task": "Search for reliable weather APIs"
|
"task": "Search for reliable weather APIs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"agent": "Web",
|
"agent": "Web",
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"need": "1",
|
"need": ["1"],
|
||||||
"task": "Obtain API key from the selected service"
|
"task": "Obtain API key from the selected service"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"agent": "File",
|
"agent": "File",
|
||||||
"id": "3",
|
"id": "3",
|
||||||
"need": null,
|
"need": [],
|
||||||
"task": "Create and setup a web app folder for a python project. initialize as a git repo with all required file and a sources folder. You are forbidden from asking clarification, just execute."
|
"task": "Create and setup a web app folder for a python project. initialize as a git repo with all required file and a sources folder. You are forbidden from asking clarification, just execute."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"agent": "Coder",
|
"agent": "Coder",
|
||||||
"id": "3",
|
"id": "3",
|
||||||
"need": "2,3",
|
"need": ["2", "3"],
|
||||||
"task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute.""
|
"task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute.""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ def setup():
|
|||||||
def get_updated_sentence():
|
def get_updated_sentence():
|
||||||
if not generator:
|
if not generator:
|
||||||
return jsonify({"error": "Generator not initialized"}), 405
|
return jsonify({"error": "Generator not initialized"}), 405
|
||||||
|
print(generator.get_status())
|
||||||
return generator.get_status()
|
return generator.get_status()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class Cache:
|
||||||
|
def __init__(self, cache_dir='.cache', cache_file='messages.json'):
|
||||||
|
self.cache_dir = Path(cache_dir)
|
||||||
|
self.cache_file = self.cache_dir / cache_file
|
||||||
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
if not self.cache_file.exists():
|
||||||
|
with open(self.cache_file, 'w') as f:
|
||||||
|
json.dump([], f)
|
||||||
|
|
||||||
|
with open(self.cache_file, 'r') as f:
|
||||||
|
self.cache = set(json.load(f))
|
||||||
|
|
||||||
|
def add_message_pair(self, user_message: str, assistant_message: str):
|
||||||
|
"""Add a user/assistant pair to the cache if not present."""
|
||||||
|
if not any(entry["user"] == user_message for entry in self.cache):
|
||||||
|
self.cache.append({"user": user_message, "assistant": assistant_message})
|
||||||
|
self._save()
|
||||||
|
|
||||||
|
def is_cached(self, user_message: str) -> bool:
|
||||||
|
"""Check if a user msg is cached."""
|
||||||
|
return any(entry["user"] == user_message for entry in self.cache)
|
||||||
|
|
||||||
|
def get_cached_response(self, user_message: str) -> str | None:
|
||||||
|
"""Return the assistant response to a user message if cached."""
|
||||||
|
for entry in self.cache:
|
||||||
|
if entry["user"] == user_message:
|
||||||
|
return entry["assistant"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
with open(self.cache_file, 'w') as f:
|
||||||
|
json.dump(self.cache, f, indent=2)
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from .cache import Cache
|
||||||
|
|
||||||
class GenerationState:
|
class GenerationState:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -29,6 +30,7 @@ class GeneratorLLM():
|
|||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
self.logger.addHandler(handler)
|
self.logger.addHandler(handler)
|
||||||
self.logger.setLevel(logging.INFO)
|
self.logger.setLevel(logging.INFO)
|
||||||
|
cache = Cache()
|
||||||
|
|
||||||
def set_model(self, model: str) -> None:
|
def set_model(self, model: str) -> None:
|
||||||
self.logger.info(f"Model set to {model}")
|
self.logger.info(f"Model set to {model}")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from .generator import GeneratorLLM
|
from .generator import GeneratorLLM
|
||||||
|
from .cache import Cache
|
||||||
import ollama
|
import ollama
|
||||||
|
|
||||||
class OllamaLLM(GeneratorLLM):
|
class OllamaLLM(GeneratorLLM):
|
||||||
@@ -10,6 +11,7 @@ class OllamaLLM(GeneratorLLM):
|
|||||||
Handle generation using Ollama.
|
Handle generation using Ollama.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.cache = Cache()
|
||||||
|
|
||||||
def generate(self, history):
|
def generate(self, history):
|
||||||
self.logger.info(f"Using {self.model} for generation with Ollama")
|
self.logger.info(f"Using {self.model} for generation with Ollama")
|
||||||
@@ -26,10 +28,10 @@ class OllamaLLM(GeneratorLLM):
|
|||||||
)
|
)
|
||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
content = chunk['message']['content']
|
content = chunk['message']['content']
|
||||||
if '\n' in content:
|
|
||||||
self.logger.info(content)
|
|
||||||
|
|
||||||
with self.state.lock:
|
with self.state.lock:
|
||||||
|
if '.' in content:
|
||||||
|
self.logger.info(self.state.current_buffer)
|
||||||
self.state.current_buffer += content
|
self.state.current_buffer += content
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+101
-64
@@ -54,7 +54,7 @@ class BrowserAgent(Agent):
|
|||||||
links_clean = []
|
links_clean = []
|
||||||
for link in links:
|
for link in links:
|
||||||
link = link.strip()
|
link = link.strip()
|
||||||
if link[-1] == '.':
|
if not (link[-1].isalpha() or link[-1].isdigit()):
|
||||||
links_clean.append(link[:-1])
|
links_clean.append(link[:-1])
|
||||||
else:
|
else:
|
||||||
links_clean.append(link)
|
links_clean.append(link)
|
||||||
@@ -70,7 +70,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 will navigate to <link>"
|
||||||
Do not explain your choice.
|
Do not explain your choice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -82,72 +82,84 @@ class BrowserAgent(Agent):
|
|||||||
notes = '\n'.join(self.notes)
|
notes = '\n'.join(self.notes)
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
You are a web browser.
|
You are navigating the web.
|
||||||
You are currently on this webpage:
|
|
||||||
|
**Current Context**
|
||||||
|
|
||||||
|
Webpage ({self.current_page}) content:
|
||||||
{page_text}
|
{page_text}
|
||||||
|
|
||||||
You can navigate to these navigation links:
|
Allowed Navigation Links:
|
||||||
{remaining_links_text}
|
{remaining_links_text}
|
||||||
|
|
||||||
Your task:
|
Inputs forms:
|
||||||
1. Decide if the current page answers the user’s query:
|
{inputs_form_text}
|
||||||
- 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 completed user request, 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.
|
|
||||||
3. Fill forms on the page:
|
|
||||||
- If user give you informations that help you fill form, fill it.
|
|
||||||
- If you don't know how to fill a form, leave it empty.
|
|
||||||
- You can fill a form using [form_name](value). Do not go back when you fill a form.
|
|
||||||
|
|
||||||
Recap of note taking:
|
End of webpage ({self.current_page}.
|
||||||
If useful -> Note: [Briefly summarize the key information or task you conducted.]
|
|
||||||
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):
|
# Instruction
|
||||||
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
|
|
||||||
|
|
||||||
Example 2 (not useful, but related link):
|
1. **Decide if the page answers the user’s query:**
|
||||||
|
- If it does, take notes of useful information (Note: ...), include relevant link in note, then move to a new page.
|
||||||
|
- If it does and you completed user request, say REQUEST_EXIT.
|
||||||
|
- If it doesn’t, say: Error: <why page don't help> then go back or navigate to another link.
|
||||||
|
2. **Navigate to a link by either: **
|
||||||
|
- Saying I will navigate to <url>: (write down the full URL, e.g., www.example.com/cats).
|
||||||
|
- Going back: If no link seems helpful, say: GO_BACK.
|
||||||
|
3. **Fill forms on the page:**
|
||||||
|
- Fill form only on relevant page with given informations. You might use form to conduct search on a page.
|
||||||
|
- You can fill a form using [form_name](value). Don't GO_BACK when filling form.
|
||||||
|
- If a form is irrelevant or you lack informations leave it empty.
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- Do not write "The page talk about ...", write your finding on the page and how they contribute to an answer.
|
||||||
|
- Put note in a single paragraph.
|
||||||
|
- When you exit, explain why.
|
||||||
|
|
||||||
|
# Example:
|
||||||
|
|
||||||
|
Example 1 (useful page, no need go futher):
|
||||||
|
Note: According to karpathy site (<link>) LeCun net is ...<expand on page content>..."
|
||||||
|
No link seem useful to provide futher information.
|
||||||
|
Action: GO_BACK
|
||||||
|
|
||||||
|
Example 2 (not useful, see useful link on page):
|
||||||
Error: reddit.com/welcome does not discuss anything related to the user’s query.
|
Error: reddit.com/welcome does not discuss anything related to the user’s query.
|
||||||
There is a link that could lead to the information, I want to navigate to http://reddit.com/r/locallama
|
There is a link that could lead to the information.
|
||||||
|
Action: navigate to http://reddit.com/r/locallama
|
||||||
|
|
||||||
Example 3 (not useful, no related links):
|
Example 3 (not useful, no related links):
|
||||||
Error: x.com does not discuss anything related to the user’s query and no navigation link are usefull.
|
Error: x.com does not discuss anything related to the user’s query and no navigation link are usefull.
|
||||||
GO_BACK
|
Action: GO_BACK
|
||||||
|
|
||||||
Example 3 (query answer found):
|
Example 3 (query answer found, enought notes taken):
|
||||||
Note: I found on github.com that agenticSeek is made by Fosowl.
|
Note: I found on <link> that ...<expand on information found>...
|
||||||
Given this information, given this I should exit the web browser. REQUEST_EXIT
|
Given this answer the user query I should exit the web browser.
|
||||||
|
Action: REQUEST_EXIT
|
||||||
|
|
||||||
Example 4 (loging form visible):
|
Example 4 (loging form visible):
|
||||||
Note: I am on the login page, I should now type the given username and password.
|
|
||||||
|
Note: I am on the login page, I will type the given username and password.
|
||||||
|
Action:
|
||||||
[username_field](David)
|
[username_field](David)
|
||||||
[password_field](edgerunners77)
|
[password_field](edgerunners77)
|
||||||
|
|
||||||
You see the following inputs forms:
|
Remember, user asked:
|
||||||
{inputs_form_text}
|
|
||||||
|
|
||||||
Remember, the user asked:
|
|
||||||
{user_prompt}
|
{user_prompt}
|
||||||
So far you took these notes:
|
You previously took these notes:
|
||||||
{notes}
|
{notes}
|
||||||
You are currently on page : {self.current_page}
|
Do not Step-by-Step explanation. Write Notes or Error as a long paragraph followed by your action.
|
||||||
Do not explain your choice.
|
You might REQUEST_EXIT if no more link are useful.
|
||||||
Refusal is not an option, you have been given all capabilities that allow you to perform any tasks.
|
Do not navigate to AI tools or search engine. Only navigate to tool if asked.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def llm_decide(self, prompt: str) -> Tuple[str, str]:
|
def llm_decide(self, prompt: str, show_reasoning: bool = False) -> Tuple[str, str]:
|
||||||
animate_thinking("Thinking...", color="status")
|
animate_thinking("Thinking...", color="status")
|
||||||
self.memory.push('user', prompt)
|
self.memory.push('user', prompt)
|
||||||
answer, reasoning = self.llm_request()
|
answer, reasoning = self.llm_request()
|
||||||
output = answer if len(answer) > 16 else f"Action: {answer}\nReasoning: {reasoning}"
|
if show_reasoning:
|
||||||
print()
|
pretty_print(reasoning, color="failure")
|
||||||
pretty_print(output, color="output")
|
pretty_print(answer, color="output")
|
||||||
print()
|
|
||||||
return answer, reasoning
|
return answer, reasoning
|
||||||
|
|
||||||
def select_unvisited(self, search_result: List[str]) -> List[str]:
|
def select_unvisited(self, search_result: List[str]) -> List[str]:
|
||||||
@@ -179,23 +191,42 @@ class BrowserAgent(Agent):
|
|||||||
def stringify_search_results(self, results_arr: List[str]) -> str:
|
def stringify_search_results(self, results_arr: List[str]) -> str:
|
||||||
return '\n\n'.join([f"Link: {res['link']}\nPreview: {res['snippet']}" for res in results_arr])
|
return '\n\n'.join([f"Link: {res['link']}\nPreview: {res['snippet']}" for res in results_arr])
|
||||||
|
|
||||||
def save_notes(self, text):
|
def parse_answer(self, text):
|
||||||
lines = text.split('\n')
|
lines = text.split('\n')
|
||||||
|
saving = False
|
||||||
|
buffer = []
|
||||||
|
links = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
if line == '' or 'action:' in line.lower():
|
||||||
|
saving = False
|
||||||
if "note" in line.lower():
|
if "note" in line.lower():
|
||||||
self.notes.append(line)
|
saving = True
|
||||||
|
if saving:
|
||||||
|
buffer.append(line.replace("notes:", ''))
|
||||||
|
else:
|
||||||
|
links.extend(self.extract_links(line))
|
||||||
|
self.notes.append('. '.join(buffer).strip())
|
||||||
|
return links
|
||||||
|
|
||||||
|
def select_link(self, links: List[str]) -> str | None:
|
||||||
|
for lk in links:
|
||||||
|
if lk == self.current_page:
|
||||||
|
continue
|
||||||
|
return lk
|
||||||
|
return None
|
||||||
|
|
||||||
def conclude_prompt(self, user_query: str) -> str:
|
def conclude_prompt(self, user_query: str) -> str:
|
||||||
annotated_notes = [f"{i+1}: {note.lower().replace('note:', '')}" for i, note in enumerate(self.notes)]
|
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
|
||||||
search_note = '\n'.join(annotated_notes)
|
search_note = '\n'.join(annotated_notes)
|
||||||
pretty_print(f"AI notes:\n{search_note}", color="success")
|
pretty_print(f"AI notes:\n{search_note}", color="success")
|
||||||
return f"""
|
return f"""
|
||||||
Following a human request:
|
Following a human request:
|
||||||
{user_query}
|
{user_query}
|
||||||
A web AI made the following finding across different pages:
|
A web browsing AI made the following finding across different pages:
|
||||||
{search_note}
|
{search_note}
|
||||||
|
|
||||||
Summarize the finding or step that lead to success, and provide a conclusion that answer the request.
|
Expand on the finding or step that lead to success, and provide a conclusion that answer the request. Include link when possible.
|
||||||
|
Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def search_prompt(self, user_prompt: str) -> str:
|
def search_prompt(self, user_prompt: str) -> str:
|
||||||
@@ -214,7 +245,8 @@ class BrowserAgent(Agent):
|
|||||||
You: "search: Recent space missions news, {self.date}"
|
You: "search: Recent space missions news, {self.date}"
|
||||||
|
|
||||||
Do not explain, do not write anything beside the search query.
|
Do not explain, do not write anything beside the search query.
|
||||||
If the query does not make any sense for a web search explain why and say REQUEST_EXIT
|
Except if query does not make any sense for a web search then explain why and say REQUEST_EXIT
|
||||||
|
Do not try to answer query. you can only formulate search term or exit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handle_update_prompt(self, user_prompt: str, page_text: str) -> str:
|
def handle_update_prompt(self, user_prompt: str, page_text: str) -> str:
|
||||||
@@ -255,58 +287,63 @@ class BrowserAgent(Agent):
|
|||||||
mem_begin_idx = self.memory.push('user', self.search_prompt(user_prompt))
|
mem_begin_idx = self.memory.push('user', self.search_prompt(user_prompt))
|
||||||
ai_prompt, _ = self.llm_request()
|
ai_prompt, _ = self.llm_request()
|
||||||
if "REQUEST_EXIT" in ai_prompt:
|
if "REQUEST_EXIT" in ai_prompt:
|
||||||
pretty_print(f"{reasoning}\n{ai_prompt}", color="output")
|
pretty_print(f"Web agent requested exit.\n{reasoning}\n\n{ai_prompt}", color="failure")
|
||||||
return ai_prompt, ""
|
return ai_prompt, ""
|
||||||
animate_thinking(f"Searching...", color="status")
|
animate_thinking(f"Searching...", color="status")
|
||||||
search_result_raw = self.tools["web_search"].execute([ai_prompt], False)
|
search_result_raw = self.tools["web_search"].execute([ai_prompt], False)
|
||||||
search_result = self.jsonify_search_results(search_result_raw)[:12] # until futher improvement
|
search_result = self.jsonify_search_results(search_result_raw)[:12]
|
||||||
self.show_search_results(search_result)
|
self.show_search_results(search_result)
|
||||||
prompt = self.make_newsearch_prompt(user_prompt, search_result)
|
prompt = self.make_newsearch_prompt(user_prompt, search_result)
|
||||||
unvisited = [None]
|
unvisited = [None]
|
||||||
while not complete:
|
while not complete:
|
||||||
answer, reasoning = self.llm_decide(prompt)
|
answer, reasoning = self.llm_decide(prompt, show_reasoning = False)
|
||||||
self.save_notes(answer)
|
|
||||||
|
|
||||||
extracted_form = self.extract_form(answer)
|
extracted_form = self.extract_form(answer)
|
||||||
if len(extracted_form) > 0:
|
if len(extracted_form) > 0:
|
||||||
|
pretty_print(f"Filling inputs form...", color="status")
|
||||||
self.browser.fill_form_inputs(extracted_form)
|
self.browser.fill_form_inputs(extracted_form)
|
||||||
self.browser.find_and_click_submission()
|
self.browser.find_and_click_submission()
|
||||||
page_text = self.browser.get_text()
|
page_text = self.browser.get_text()
|
||||||
answer = self.handle_update_prompt(user_prompt, page_text)
|
answer = self.handle_update_prompt(user_prompt, page_text)
|
||||||
answer, reasoning = self.llm_decide(prompt)
|
answer, reasoning = self.llm_decide(prompt)
|
||||||
|
|
||||||
|
links = self.parse_answer(answer)
|
||||||
|
link = self.select_link(links)
|
||||||
|
self.search_history.append(link)
|
||||||
|
|
||||||
if "REQUEST_EXIT" in answer:
|
if "REQUEST_EXIT" in answer:
|
||||||
|
pretty_print(f"Agent requested exit.", color="status")
|
||||||
complete = True
|
complete = True
|
||||||
break
|
break
|
||||||
|
|
||||||
links = self.extract_links(answer)
|
|
||||||
if len(unvisited) == 0:
|
if len(unvisited) == 0:
|
||||||
|
pretty_print(f"Visited all links.", color="status")
|
||||||
break
|
break
|
||||||
|
|
||||||
if "FORM_FILLED" in answer:
|
if "FORM_FILLED" in answer:
|
||||||
|
pretty_print(f"Filled form. Handling page update.", color="status")
|
||||||
page_text = self.browser.get_text()
|
page_text = self.browser.get_text()
|
||||||
self.navigable_links = self.browser.get_navigable()
|
self.navigable_links = self.browser.get_navigable()
|
||||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(links) == 0 or "GO_BACK" in answer:
|
if link == None or "GO_BACK" in answer:
|
||||||
|
pretty_print(f"Going back to results. Still {len(unvisited)}", color="status")
|
||||||
unvisited = self.select_unvisited(search_result)
|
unvisited = self.select_unvisited(search_result)
|
||||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||||
pretty_print(f"Going back to results. Still {len(unvisited)}", color="warning")
|
|
||||||
links = []
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
animate_thinking(f"Navigating to {links[0]}", color="status")
|
animate_thinking(f"Navigating to {link}", color="status")
|
||||||
if speech_module: speech_module.speak(f"Navigating to {links[0]}")
|
if speech_module: speech_module.speak(f"Navigating to {link}")
|
||||||
self.browser.go_to(links[0])
|
self.browser.go_to(link)
|
||||||
self.current_page = links[0]
|
self.current_page = link
|
||||||
self.search_history.append(links[0])
|
|
||||||
page_text = self.browser.get_text()
|
page_text = self.browser.get_text()
|
||||||
self.navigable_links = self.browser.get_navigable()
|
self.navigable_links = self.browser.get_navigable()
|
||||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||||
|
|
||||||
|
pretty_print("Exited navigation, starting to summarize finding...", color="status")
|
||||||
prompt = self.conclude_prompt(user_prompt)
|
prompt = self.conclude_prompt(user_prompt)
|
||||||
mem_last_idx = self.memory.push('assistant', prompt)
|
mem_last_idx = self.memory.push('user', prompt)
|
||||||
answer, reasoning = self.llm_request()
|
answer, reasoning = self.llm_request()
|
||||||
pretty_print(answer, color="output")
|
pretty_print(answer, color="output")
|
||||||
self.memory.clear_section(mem_begin_idx, mem_last_idx)
|
self.memory.clear_section(mem_begin_idx, mem_last_idx)
|
||||||
|
|||||||
@@ -29,10 +29,4 @@ class CasualAgent(Agent):
|
|||||||
return answer, reasoning
|
return answer, reasoning
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from llm_provider import Provider
|
pass
|
||||||
|
|
||||||
#local_provider = Provider("ollama", "deepseek-r1:14b", None)
|
|
||||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
|
||||||
agent = CasualAgent("deepseek-r1:14b", "jarvis", "prompts/casual_agent.txt", server_provider)
|
|
||||||
ans = agent.process("Hello, how are you?")
|
|
||||||
print(ans)
|
|
||||||
@@ -35,7 +35,7 @@ class CoderAgent(Agent):
|
|||||||
info = f"System Info:\n" \
|
info = f"System Info:\n" \
|
||||||
f"OS: {platform.system()} {platform.release()}\n" \
|
f"OS: {platform.system()} {platform.release()}\n" \
|
||||||
f"Python Version: {platform.python_version()}\n" \
|
f"Python Version: {platform.python_version()}\n" \
|
||||||
f"\nYou must work in directory: {self.work_dir}"
|
f"\nYou must save file in work directory: {self.work_dir}"
|
||||||
return f"{prompt}\n\n{info}"
|
return f"{prompt}\n\n{info}"
|
||||||
|
|
||||||
def process(self, prompt, speech_module) -> str:
|
def process(self, prompt, speech_module) -> str:
|
||||||
@@ -51,7 +51,7 @@ class CoderAgent(Agent):
|
|||||||
self.wait_message(speech_module)
|
self.wait_message(speech_module)
|
||||||
answer, reasoning = self.llm_request()
|
answer, reasoning = self.llm_request()
|
||||||
if clarify_trigger in answer:
|
if clarify_trigger in answer:
|
||||||
return answer.replace(clarify_trigger, ""), reasoning
|
return answer, reasoning
|
||||||
if not "```" in answer:
|
if not "```" in answer:
|
||||||
self.last_answer = answer
|
self.last_answer = answer
|
||||||
break
|
break
|
||||||
@@ -68,10 +68,4 @@ class CoderAgent(Agent):
|
|||||||
return answer, reasoning
|
return answer, reasoning
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from llm_provider import Provider
|
pass
|
||||||
|
|
||||||
#local_provider = Provider("ollama", "deepseek-r1:14b", None)
|
|
||||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
|
||||||
agent = CoderAgent("deepseek-r1:14b", "jarvis", "prompts/coder_agent.txt", server_provider)
|
|
||||||
ans = agent.process("What is the output of 5+5 in python ?")
|
|
||||||
print(ans)
|
|
||||||
@@ -36,10 +36,4 @@ class FileAgent(Agent):
|
|||||||
return answer, reasoning
|
return answer, reasoning
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from llm_provider import Provider
|
pass
|
||||||
|
|
||||||
#local_provider = Provider("ollama", "deepseek-r1:14b", None)
|
|
||||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
|
||||||
agent = FileAgent("deepseek-r1:14b", "jarvis", "prompts/file_agent.txt", server_provider)
|
|
||||||
ans = agent.process("What is the content of the file toto.py ?")
|
|
||||||
print(ans)
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
|
from typing import List, Tuple, Type, Dict
|
||||||
from sources.utility import pretty_print, animate_thinking
|
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.browser_agent import BrowserAgent
|
from sources.agents.browser_agent import BrowserAgent
|
||||||
|
from sources.text_to_speech import Speech
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class PlannerAgent(Agent):
|
class PlannerAgent(Agent):
|
||||||
@@ -61,63 +63,80 @@ class PlannerAgent(Agent):
|
|||||||
return zip(names, tasks)
|
return zip(names, tasks)
|
||||||
return zip(tasks_names, tasks)
|
return zip(tasks_names, tasks)
|
||||||
|
|
||||||
def make_prompt(self, task, needed_infos):
|
def make_prompt(self, task: dict, agent_infos_dict: dict):
|
||||||
if needed_infos is None:
|
infos = ""
|
||||||
needed_infos = "No needed informations."
|
if agent_infos_dict is None or len(agent_infos_dict) == 0:
|
||||||
|
infos = "No needed informations."
|
||||||
|
else:
|
||||||
|
for agent_id, info in agent_infos_dict.items():
|
||||||
|
infos += f"\t- According to agent {agent_id}:\n{info}\n\n"
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
You are given the following informations:
|
You are given informations from your AI friends work:
|
||||||
{needed_infos}
|
{infos}
|
||||||
Your task is:
|
Your task is:
|
||||||
{task}
|
{task}
|
||||||
"""
|
"""
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
def show_plan(self, json_plan):
|
def show_plan(self, json_plan: dict) -> None:
|
||||||
agents_tasks = self.parse_agent_tasks(json_plan)
|
agents_tasks = self.parse_agent_tasks(json_plan)
|
||||||
if agents_tasks == (None, None):
|
if agents_tasks == (None, None):
|
||||||
|
pretty_print("Failed to make a plan.", color="failure")
|
||||||
return
|
return
|
||||||
pretty_print("▂▘ P L A N ▝▂", color="output")
|
pretty_print("\n▂▘ P L A N ▝▂", color="status")
|
||||||
for task_name, task in agents_tasks:
|
for task_name, task in agents_tasks:
|
||||||
pretty_print(f"{task['agent']} -> {task['task']}", color="info")
|
pretty_print(f"{task['agent']} -> {task['task']}", color="info")
|
||||||
pretty_print("▔▗ E N D ▖▔", color="output")
|
pretty_print("▔▗ E N D ▖▔", color="status")
|
||||||
|
|
||||||
def process(self, prompt, speech_module) -> str:
|
def make_plan(self, prompt: str) -> str:
|
||||||
ok = False
|
ok = False
|
||||||
agents_tasks = (None, None)
|
answer = None
|
||||||
while not ok:
|
while not ok:
|
||||||
self.wait_message(speech_module)
|
|
||||||
animate_thinking("Thinking...", color="status")
|
animate_thinking("Thinking...", color="status")
|
||||||
self.memory.push('user', prompt)
|
self.memory.push('user', prompt)
|
||||||
answer, _ = self.llm_request()
|
answer, _ = self.llm_request()
|
||||||
pretty_print(answer.split('\n')[0], color="output")
|
for line in answer.split('\n'):
|
||||||
|
if "```json" in line:
|
||||||
|
break
|
||||||
|
pretty_print(line, color="output")
|
||||||
self.show_plan(answer)
|
self.show_plan(answer)
|
||||||
ok_str = input("Is the plan ok? (y/n): ")
|
ok_str = input("Is the plan ok? (y/n): ")
|
||||||
if ok_str == 'y':
|
if ok_str == 'y':
|
||||||
ok = True
|
ok = True
|
||||||
else:
|
else:
|
||||||
prompt = input("Please reformulate: ")
|
prompt = input("Please reformulate: ")
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def start_agent_process(self, task: str, required_infos: dict | None) -> str:
|
||||||
|
agent_prompt = self.make_prompt(task['task'], required_infos)
|
||||||
|
pretty_print(f"Agent {task['agent']} started working...", color="status")
|
||||||
|
agent_answer, _ = self.agents[task['agent'].lower()].process(agent_prompt, None)
|
||||||
|
self.agents[task['agent'].lower()].show_answer()
|
||||||
|
pretty_print(f"Agent {task['agent']} completed task.", color="status")
|
||||||
|
return agent_answer
|
||||||
|
|
||||||
|
def get_work_result_agent(self, task_needs, agents_work_result):
|
||||||
|
return {k: agents_work_result[k] for k in task_needs if k in agents_work_result}
|
||||||
|
|
||||||
|
def process(self, prompt: str, speech_module: Speech) -> str:
|
||||||
|
agents_tasks = (None, None)
|
||||||
|
agents_work_result = dict()
|
||||||
|
|
||||||
|
answer = self.make_plan(prompt)
|
||||||
agents_tasks = self.parse_agent_tasks(answer)
|
agents_tasks = self.parse_agent_tasks(answer)
|
||||||
|
|
||||||
if agents_tasks == (None, None):
|
if agents_tasks == (None, None):
|
||||||
return "Failed to parse the tasks", reasoning
|
return "Failed to parse the tasks.", reasoning
|
||||||
prev_agent_answer = None
|
|
||||||
for task_name, task in agents_tasks:
|
for task_name, task in agents_tasks:
|
||||||
pretty_print(f"I will {task_name}.", color="info")
|
pretty_print(f"I will {task_name}.", color="info")
|
||||||
agent_prompt = self.make_prompt(task['task'], prev_agent_answer)
|
|
||||||
pretty_print(f"Assigned agent {task['agent']} to {task_name}", color="info")
|
pretty_print(f"Assigned agent {task['agent']} to {task_name}", color="info")
|
||||||
if speech_module: speech_module.speak(f"I will {task_name}. I assigned the {task['agent']} agent to the task.")
|
if speech_module: speech_module.speak(f"I will {task_name}. I assigned the {task['agent']} agent to the task.")
|
||||||
|
|
||||||
|
if agents_work_result is not None:
|
||||||
|
required_infos = self.get_work_result_agent(task['need'], agents_work_result)
|
||||||
try:
|
try:
|
||||||
prev_agent_answer, _ = self.agents[task['agent'].lower()].process(agent_prompt, speech_module)
|
self.last_answer = self.start_agent_process(task, required_infos)
|
||||||
pretty_print(f"-- Agent answer ---\n\n", color="output")
|
|
||||||
self.agents[task['agent'].lower()].show_answer()
|
|
||||||
pretty_print(f"\n\n", color="output")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
self.last_answer = prev_agent_answer
|
agents_work_result[task['id']] = self.last_answer
|
||||||
return prev_agent_answer, ""
|
return self.last_answer, ""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from llm_provider import Provider
|
|
||||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
|
||||||
agent = PlannerAgent("deepseek-r1:14b", "jarvis", "prompts/planner_agent.txt", server_provider)
|
|
||||||
ans = agent.process("Make a cool game to illustrate the current relation between USA and europe")
|
|
||||||
+39
-39
@@ -6,7 +6,7 @@ from selenium.webdriver.support.ui import WebDriverWait
|
|||||||
from selenium.webdriver.support import expected_conditions as EC
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
from selenium.common.exceptions import TimeoutException, WebDriverException
|
from selenium.common.exceptions import TimeoutException, WebDriverException
|
||||||
from selenium.webdriver.common.action_chains import ActionChains
|
from selenium.webdriver.common.action_chains import ActionChains
|
||||||
from typing import List, Tuple, Type, Dict, Tuple
|
from typing import List, Tuple, Type, Dict
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from fake_useragent import UserAgent
|
from fake_useragent import UserAgent
|
||||||
@@ -18,19 +18,14 @@ import random
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import markdownify
|
import markdownify
|
||||||
import logging
|
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
if __name__ == "__main__":
|
from sources.utility import pretty_print, animate_thinking
|
||||||
from utility import pretty_print, animate_thinking
|
from sources.logger import Logger
|
||||||
else:
|
|
||||||
from sources.utility import pretty_print, animate_thinking
|
|
||||||
|
|
||||||
logging.basicConfig(filename='browser.log', level=logging.ERROR,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
|
|
||||||
def get_chrome_path() -> str:
|
def get_chrome_path() -> str:
|
||||||
|
"""Get the path to the Chrome executable."""
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
paths = [
|
paths = [
|
||||||
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||||
@@ -111,14 +106,11 @@ class Browser:
|
|||||||
"""Initialize the browser with optional AntiCaptcha installation."""
|
"""Initialize the browser with optional AntiCaptcha installation."""
|
||||||
self.js_scripts_folder = "./sources/web_scripts/" if not __name__ == "__main__" else "./web_scripts/"
|
self.js_scripts_folder = "./sources/web_scripts/" if not __name__ == "__main__" else "./web_scripts/"
|
||||||
self.anticaptcha = "https://chrome.google.com/webstore/detail/nopecha-captcha-solver/dknlfmjaanfblgfdfebhijalfmhmjjjo/related"
|
self.anticaptcha = "https://chrome.google.com/webstore/detail/nopecha-captcha-solver/dknlfmjaanfblgfdfebhijalfmhmjjjo/related"
|
||||||
|
self.logger = Logger("browser.log")
|
||||||
try:
|
try:
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
self.wait = WebDriverWait(self.driver, 10)
|
self.wait = WebDriverWait(self.driver, 10)
|
||||||
self.logger = logging.getLogger(__name__)
|
|
||||||
self.logger.info("Browser initialized successfully")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.basicConfig(filename='browser.log', level=logging.ERROR,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
raise Exception(f"Failed to initialize browser: {str(e)}")
|
raise Exception(f"Failed to initialize browser: {str(e)}")
|
||||||
self.driver.get("https://www.google.com")
|
self.driver.get("https://www.google.com")
|
||||||
if anticaptcha_manual_install:
|
if anticaptcha_manual_install:
|
||||||
@@ -142,7 +134,7 @@ class Browser:
|
|||||||
message="stuck on 'checking browser' or verification screen"
|
message="stuck on 'checking browser' or verification screen"
|
||||||
)
|
)
|
||||||
self.apply_web_safety()
|
self.apply_web_safety()
|
||||||
self.logger.info(f"Navigated to: {url}")
|
self.logger.log(f"Navigated to: {url}")
|
||||||
return True
|
return True
|
||||||
except TimeoutException as e:
|
except TimeoutException as e:
|
||||||
self.logger.error(f"Timeout waiting for {url} to load: {str(e)}")
|
self.logger.error(f"Timeout waiting for {url} to load: {str(e)}")
|
||||||
@@ -167,19 +159,29 @@ class Browser:
|
|||||||
return (word_count >= 5 and (has_punctuation or is_long_enough))
|
return (word_count >= 5 and (has_punctuation or is_long_enough))
|
||||||
|
|
||||||
def get_text(self) -> str | None:
|
def get_text(self) -> str | None:
|
||||||
"""Get page text and convert it to README (Markdown) format."""
|
"""Get page text as formatted Markdown"""
|
||||||
try:
|
try:
|
||||||
soup = BeautifulSoup(self.driver.page_source, 'html.parser')
|
soup = BeautifulSoup(self.driver.page_source, 'html.parser')
|
||||||
|
for element in soup(['script', 'style', 'noscript', 'meta', 'link']):
|
||||||
for element in soup(['script', 'style']):
|
|
||||||
element.decompose()
|
element.decompose()
|
||||||
|
markdown_converter = markdownify.MarkdownConverter(
|
||||||
text = soup.get_text()
|
heading_style="ATX",
|
||||||
lines = (f"{line.strip()}\n" for line in text.splitlines())
|
strip=['a'],
|
||||||
text = "\n".join(chunk for chunk in lines if chunk and self.is_sentence(chunk))
|
autolinks=False,
|
||||||
text = text[:4096]
|
bullets='•',
|
||||||
#markdown_text = markdownify.markdownify(text, heading_style="ATX")
|
strong_em_symbol='*',
|
||||||
return "[Start of page]\n" + text + "\n[End of page]"
|
default_title=False,
|
||||||
|
)
|
||||||
|
markdown_text = markdown_converter.convert(str(soup.body))
|
||||||
|
lines = []
|
||||||
|
for line in markdown_text.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped and self.is_sentence(stripped):
|
||||||
|
cleaned = ' '.join(stripped.split())
|
||||||
|
lines.append(cleaned)
|
||||||
|
result = "[Start of page]\n\n" + "\n\n".join(lines) + "\n\n[End of page]"
|
||||||
|
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
|
||||||
|
return result[:8192]
|
||||||
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
|
||||||
@@ -247,20 +249,25 @@ class Browser:
|
|||||||
if not element.is_enabled():
|
if not element.is_enabled():
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
|
self.logger.error(f"Scrolling to element for click_element.")
|
||||||
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", element)
|
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", element)
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
element.click()
|
element.click()
|
||||||
return True
|
return True
|
||||||
except ElementClickInterceptedException as e:
|
except ElementClickInterceptedException as e:
|
||||||
|
self.logger.error(f"Error click_element: {str(e)}")
|
||||||
return False
|
return False
|
||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
|
self.logger.warning(f"Timeout clicking element.")
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Unexpected error clicking element at {xpath}: {str(e)}")
|
self.logger.error(f"Unexpected error clicking element at {xpath}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load_js(self, file_name: str) -> str:
|
def load_js(self, file_name: str) -> str:
|
||||||
|
"""Load javascript from script folder to inject to page."""
|
||||||
path = os.path.join(self.js_scripts_folder, file_name)
|
path = os.path.join(self.js_scripts_folder, file_name)
|
||||||
|
self.logger.info(f"Loading js at {path}")
|
||||||
try:
|
try:
|
||||||
with open(path, 'r') as f:
|
with open(path, 'r') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
@@ -270,6 +277,7 @@ class Browser:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
def find_all_inputs(self, timeout=3):
|
def find_all_inputs(self, timeout=3):
|
||||||
|
"""Find all inputs elements on the page."""
|
||||||
try:
|
try:
|
||||||
WebDriverWait(self.driver, timeout).until(
|
WebDriverWait(self.driver, timeout).until(
|
||||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||||
@@ -287,6 +295,7 @@ class Browser:
|
|||||||
try:
|
try:
|
||||||
input_elements = self.find_all_inputs()
|
input_elements = self.find_all_inputs()
|
||||||
if not input_elements:
|
if not input_elements:
|
||||||
|
self.logger.info("No input element on page.")
|
||||||
return ["No input forms found on the page."]
|
return ["No input forms found on the page."]
|
||||||
|
|
||||||
form_strings = []
|
form_strings = []
|
||||||
@@ -335,14 +344,7 @@ class Browser:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def find_and_click_btn(self, btn_type: str = 'login', timeout: int = 10) -> bool:
|
def find_and_click_btn(self, btn_type: str = 'login', timeout: int = 10) -> bool:
|
||||||
"""
|
"""Find and click a submit button matching the specified type."""
|
||||||
Find and click a submit button matching the specified type.
|
|
||||||
Args:
|
|
||||||
btn_type: The type of button to find.
|
|
||||||
timeout: time to wait for button to appear.
|
|
||||||
Returns:
|
|
||||||
bool: True if the button was found and clicked, False otherwise.
|
|
||||||
"""
|
|
||||||
buttons = self.get_buttons_xpath()
|
buttons = self.get_buttons_xpath()
|
||||||
if not buttons:
|
if not buttons:
|
||||||
self.logger.warning("No visible buttons found")
|
self.logger.warning("No visible buttons found")
|
||||||
@@ -450,21 +452,19 @@ class Browser:
|
|||||||
input_elements = self.driver.execute_script(script)
|
input_elements = self.driver.execute_script(script)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.basicConfig(level=logging.INFO)
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
driver = create_driver()
|
driver = create_driver()
|
||||||
browser = Browser(driver, anticaptcha_manual_install=True)
|
browser = Browser(driver, anticaptcha_manual_install=True)
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
#browser.go_to("https://coinmarketcap.com/")
|
#browser.go_to("https://github.com/Fosowl/agenticSeek")
|
||||||
#txt = browser.get_text()
|
#txt = browser.get_text()
|
||||||
#print(txt)
|
#print(txt)
|
||||||
|
#time.sleep(10)
|
||||||
|
#browser.go_to("https://practicetestautomation.com/practice-test-login/")
|
||||||
print("AntiCaptcha / Form Test")
|
print("AntiCaptcha / Form Test")
|
||||||
browser.go_to("https://www.google.com/recaptcha/api2/demo")
|
browser.go_to("https://www.google.com/recaptcha/api2/demo")
|
||||||
#browser.go_to("https://practicetestautomation.com/practice-test-login/")
|
|
||||||
time.sleep(10)
|
|
||||||
inputs = browser.get_form_inputs()
|
inputs = browser.get_form_inputs()
|
||||||
inputs = ['[input1](Martin)', f'[input2](Test)', '[input3](test@gmail.com)']
|
#inputs = ['[input1](Martin)', f'[input2](Test)', '[input3](test@gmail.com)']
|
||||||
browser.fill_form_inputs(inputs)
|
browser.fill_form_inputs(inputs)
|
||||||
browser.find_and_click_submission()
|
browser.find_and_click_submission()
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from typing import List, Tuple, Type, Dict, Tuple
|
from typing import List, Tuple, Type, Dict
|
||||||
|
|
||||||
from sources.text_to_speech import Speech
|
from sources.text_to_speech import Speech
|
||||||
from sources.utility import pretty_print, animate_thinking
|
from sources.utility import pretty_print, animate_thinking
|
||||||
from sources.router import AgentRouter
|
from sources.router import AgentRouter
|
||||||
from sources.speech_to_text import AudioTranscriber, AudioRecorder
|
from sources.speech_to_text import AudioTranscriber, AudioRecorder
|
||||||
|
|
||||||
|
|
||||||
class Interaction:
|
class Interaction:
|
||||||
"""
|
"""
|
||||||
Interaction is a class that handles the interaction between the user and the agents.
|
Interaction is a class that handles the interaction between the user and the agents.
|
||||||
@@ -112,17 +113,20 @@ class Interaction:
|
|||||||
|
|
||||||
def think(self) -> bool:
|
def think(self) -> bool:
|
||||||
"""Request AI agents to process the user input."""
|
"""Request AI agents to process the user input."""
|
||||||
|
push_last_agent_memory = False
|
||||||
if self.last_query is None or len(self.last_query) == 0:
|
if self.last_query is None or len(self.last_query) == 0:
|
||||||
return False
|
return False
|
||||||
agent = self.router.select_agent(self.last_query)
|
agent = self.router.select_agent(self.last_query)
|
||||||
if agent is None:
|
if agent is None:
|
||||||
return False
|
return False
|
||||||
if self.current_agent != agent and self.last_answer is not None:
|
if self.current_agent != agent and self.last_answer is not None:
|
||||||
|
push_last_agent_memory = True
|
||||||
|
tmp = self.last_answer
|
||||||
|
self.current_agent = agent
|
||||||
|
self.last_answer, _ = agent.process(self.last_query, self.speech)
|
||||||
|
if push_last_agent_memory:
|
||||||
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.current_agent.memory.push('assistant', self.last_answer)
|
||||||
self.current_agent = agent
|
|
||||||
tmp = self.last_answer
|
|
||||||
self.last_answer, _ = agent.process(self.last_query, self.speech)
|
|
||||||
if self.last_answer == tmp:
|
if self.last_answer == tmp:
|
||||||
self.last_answer = None
|
self.last_answer = None
|
||||||
return True
|
return True
|
||||||
|
|||||||
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
from typing import List, Tuple, Type, Dict, Tuple
|
from typing import List, Tuple, Type, Dict
|
||||||
import re
|
import re
|
||||||
import langid
|
import langid
|
||||||
import nltk
|
import nltk
|
||||||
@@ -6,6 +6,7 @@ from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
|||||||
from transformers import MarianMTModel, MarianTokenizer
|
from transformers import MarianMTModel, MarianTokenizer
|
||||||
|
|
||||||
from sources.utility import pretty_print, animate_thinking
|
from sources.utility import pretty_print, animate_thinking
|
||||||
|
from sources.logger import Logger
|
||||||
|
|
||||||
class LanguageUtility:
|
class LanguageUtility:
|
||||||
"""LanguageUtility for language, or emotion identification"""
|
"""LanguageUtility for language, or emotion identification"""
|
||||||
@@ -13,6 +14,7 @@ class LanguageUtility:
|
|||||||
self.sid = None
|
self.sid = None
|
||||||
self.translators_tokenizer = None
|
self.translators_tokenizer = None
|
||||||
self.translators_model = None
|
self.translators_model = None
|
||||||
|
self.logger = Logger("language.log")
|
||||||
self.load_model()
|
self.load_model()
|
||||||
|
|
||||||
def load_model(self) -> None:
|
def load_model(self) -> None:
|
||||||
@@ -40,6 +42,7 @@ class LanguageUtility:
|
|||||||
"""
|
"""
|
||||||
langid.set_languages(['fr', 'en', 'zh'])
|
langid.set_languages(['fr', 'en', 'zh'])
|
||||||
lang, score = langid.classify(text)
|
lang, score = langid.classify(text)
|
||||||
|
self.logger.info(f"Identified: {text} as {lang} with conf {score}")
|
||||||
return lang
|
return lang
|
||||||
|
|
||||||
def translate(self, text: str, origin_lang: str) -> str:
|
def translate(self, text: str, origin_lang: str) -> str:
|
||||||
@@ -86,6 +89,7 @@ class LanguageUtility:
|
|||||||
dominant_emotion = max(emotions, key=emotions.get)
|
dominant_emotion = max(emotions, key=emotions.get)
|
||||||
if emotions[dominant_emotion] == 0:
|
if emotions[dominant_emotion] == 0:
|
||||||
return 'Neutral'
|
return 'Neutral'
|
||||||
|
self.logger.info(f"Emotion: {dominant_emotion} for text: {text}")
|
||||||
return dominant_emotion
|
return dominant_emotion
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
+10
-17
@@ -13,6 +13,7 @@ from openai import OpenAI
|
|||||||
from huggingface_hub import InferenceClient
|
from huggingface_hub import InferenceClient
|
||||||
from typing import List, Tuple, Type, Dict
|
from typing import List, Tuple, Type, Dict
|
||||||
from sources.utility import pretty_print, animate_thinking
|
from sources.utility import pretty_print, animate_thinking
|
||||||
|
from sources.logger import Logger
|
||||||
|
|
||||||
class Provider:
|
class Provider:
|
||||||
def __init__(self, provider_name, model, server_address = "127.0.0.1:5000", is_local=False):
|
def __init__(self, provider_name, model, server_address = "127.0.0.1:5000", is_local=False):
|
||||||
@@ -30,6 +31,7 @@ class Provider:
|
|||||||
"dsk_deepseek": self.dsk_deepseek,
|
"dsk_deepseek": self.dsk_deepseek,
|
||||||
"test": self.test_fn
|
"test": self.test_fn
|
||||||
}
|
}
|
||||||
|
self.logger = Logger("provider.log")
|
||||||
self.api_key = None
|
self.api_key = None
|
||||||
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek"]
|
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek"]
|
||||||
if self.provider_name not in self.available_providers:
|
if self.provider_name not in self.available_providers:
|
||||||
@@ -50,6 +52,7 @@ class Provider:
|
|||||||
if not api_key:
|
if not api_key:
|
||||||
api_key = input(f"Please enter your {provider} API key: ")
|
api_key = input(f"Please enter your {provider} API key: ")
|
||||||
set_key(".env", api_key_var, api_key)
|
set_key(".env", api_key_var, api_key)
|
||||||
|
self.logger.info("Set API key in env.")
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
@@ -73,8 +76,12 @@ class Provider:
|
|||||||
Use the choosen provider to generate text.
|
Use the choosen provider to generate text.
|
||||||
"""
|
"""
|
||||||
llm = self.available_providers[self.provider_name]
|
llm = self.available_providers[self.provider_name]
|
||||||
|
self.logger.info(f"Using provider: {self.provider_name} at {self.server_ip}")
|
||||||
try:
|
try:
|
||||||
thought = llm(history, verbose)
|
thought = llm(history, verbose)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.logger.warning("User interrupted the operation with Ctrl+C")
|
||||||
|
return "Operation interrupted by user. REQUEST_EXIT"
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
raise ConnectionError(f"{str(e)}\nConnection to {self.server_ip} failed.")
|
raise ConnectionError(f"{str(e)}\nConnection to {self.server_ip} failed.")
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
@@ -98,6 +105,7 @@ class Provider:
|
|||||||
if output.returncode == 0:
|
if output.returncode == 0:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
self.logger.error(f"Ping command returned code: {output.returncode}")
|
||||||
return False
|
return False
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
return False
|
return False
|
||||||
@@ -287,26 +295,11 @@ class Provider:
|
|||||||
This function is used to conduct tests.
|
This function is used to conduct tests.
|
||||||
"""
|
"""
|
||||||
thought = """
|
thought = """
|
||||||
hello!
|
\n\n```json\n{\n \"plan\": [\n {\n \"agent\": \"Web\",\n \"id\": \"1\",\n \"need\": null,\n \"task\": \"Conduct a comprehensive web search to identify at least five AI startups located in Osaka. Use reliable sources and websites such as Crunchbase, TechCrunch, or local Japanese business directories. Capture the company names, their websites, areas of expertise, and any other relevant details.\"\n },\n {\n \"agent\": \"Web\",\n \"id\": \"2\",\n \"need\": null,\n \"task\": \"Perform a similar search to find at least five AI startups in Tokyo. Again, use trusted sources like Crunchbase, TechCrunch, or Japanese business news websites. Gather the same details as for Osaka: company names, websites, areas of focus, and additional information.\"\n },\n {\n \"agent\": \"File\",\n \"id\": \"3\",\n \"need\": [\"1\", \"2\"],\n \"task\": \"Create a new text file named research_japan.txt in the user's home directory. Organize the data collected from both searches into this file, ensuring it is well-structured and formatted for readability. Include headers for Osaka and Tokyo sections, followed by the details of each startup found.\"\n }\n ]\n}\n```
|
||||||
```python
|
|
||||||
print("Hello world from python")
|
|
||||||
```
|
|
||||||
|
|
||||||
This is ls -la from bash.
|
|
||||||
```bash
|
|
||||||
ls -la
|
|
||||||
```
|
|
||||||
|
|
||||||
This is pwd from bash.
|
|
||||||
```bash
|
|
||||||
pwd
|
|
||||||
```
|
|
||||||
|
|
||||||
goodbye!
|
|
||||||
"""
|
"""
|
||||||
return thought
|
return thought
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
provider = Provider("ollama", "deepseek-r1:1.5b", "127.0.0.1:11434")
|
provider = Provider("server", "deepseek-r1:14b", "192.168.1.20:3333")
|
||||||
res = provider.respond(["user", "Hello, how are you?"])
|
res = provider.respond(["user", "Hello, how are you?"])
|
||||||
print("Response:", res)
|
print("Response:", res)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import os, sys
|
||||||
|
from typing import List, Tuple, Type, Dict
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self, log_filename):
|
||||||
|
self.folder = '.logs'
|
||||||
|
self.create_folder(self.folder)
|
||||||
|
self.log_path = os.path.join(self.folder, log_filename)
|
||||||
|
self.enabled = True
|
||||||
|
self.logger = None
|
||||||
|
if self.enabled:
|
||||||
|
self.create_logging(log_filename)
|
||||||
|
|
||||||
|
def create_logging(self, log_filename):
|
||||||
|
self.logger = logging.getLogger(log_filename)
|
||||||
|
self.logger.setLevel(logging.DEBUG)
|
||||||
|
if not self.logger.handlers:
|
||||||
|
file_handler = logging.FileHandler(self.log_path)
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
self.logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
def create_folder(self, path):
|
||||||
|
"""Create log dir"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.enabled = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def log(self, message, level=logging.INFO):
|
||||||
|
if self.enabled:
|
||||||
|
self.logger.log(level, message)
|
||||||
|
|
||||||
|
def info(self, message):
|
||||||
|
self.log(message)
|
||||||
|
|
||||||
|
def error(self, message):
|
||||||
|
self.log(message, level=logging.ERROR)
|
||||||
|
|
||||||
|
def warning(self, message):
|
||||||
|
self.log(message, level=logging.WARN)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
lg = Logger("test.log")
|
||||||
|
lg.log("hello")
|
||||||
|
lg2 = Logger("toto.log")
|
||||||
|
lg2.log("yo")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+14
-4
@@ -4,13 +4,12 @@ import uuid
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
from typing import List, Tuple, Type, Dict, Tuple
|
from typing import List, Tuple, Type, Dict
|
||||||
import torch
|
import torch
|
||||||
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
|
|
||||||
from sources.utility import timer_decorator, pretty_print
|
from sources.utility import timer_decorator, pretty_print
|
||||||
|
from sources.logger import Logger
|
||||||
|
|
||||||
class Memory():
|
class Memory():
|
||||||
"""
|
"""
|
||||||
@@ -23,6 +22,7 @@ class Memory():
|
|||||||
self.memory = []
|
self.memory = []
|
||||||
self.memory = [{'role': 'system', 'content': system_prompt}]
|
self.memory = [{'role': 'system', 'content': system_prompt}]
|
||||||
|
|
||||||
|
self.logger = Logger("memory.log")
|
||||||
self.session_time = datetime.datetime.now()
|
self.session_time = datetime.datetime.now()
|
||||||
self.session_id = str(uuid.uuid4())
|
self.session_id = str(uuid.uuid4())
|
||||||
self.conversation_folder = f"conversations/"
|
self.conversation_folder = f"conversations/"
|
||||||
@@ -44,6 +44,7 @@ class Memory():
|
|||||||
def save_memory(self, agent_type: str = "casual_agent") -> None:
|
def save_memory(self, agent_type: str = "casual_agent") -> None:
|
||||||
"""Save the session memory to a file."""
|
"""Save the session memory to a file."""
|
||||||
if not os.path.exists(self.conversation_folder):
|
if not os.path.exists(self.conversation_folder):
|
||||||
|
self.logger.info(f"Created folder {self.conversation_folder}.")
|
||||||
os.makedirs(self.conversation_folder)
|
os.makedirs(self.conversation_folder)
|
||||||
save_path = os.path.join(self.conversation_folder, agent_type)
|
save_path = os.path.join(self.conversation_folder, agent_type)
|
||||||
if not os.path.exists(save_path):
|
if not os.path.exists(save_path):
|
||||||
@@ -52,6 +53,7 @@ class Memory():
|
|||||||
path = os.path.join(save_path, filename)
|
path = os.path.join(save_path, filename)
|
||||||
json_memory = json.dumps(self.memory)
|
json_memory = json.dumps(self.memory)
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
|
self.logger.info(f"Saved memory json at {path}")
|
||||||
f.write(json_memory)
|
f.write(json_memory)
|
||||||
|
|
||||||
def find_last_session_path(self, path) -> str:
|
def find_last_session_path(self, path) -> str:
|
||||||
@@ -63,6 +65,7 @@ class Memory():
|
|||||||
saved_sessions.append((filename, date))
|
saved_sessions.append((filename, date))
|
||||||
saved_sessions.sort(key=lambda x: x[1], reverse=True)
|
saved_sessions.sort(key=lambda x: x[1], reverse=True)
|
||||||
if len(saved_sessions) > 0:
|
if len(saved_sessions) > 0:
|
||||||
|
self.logger.info(f"Last session found at {saved_sessions[0][0]}")
|
||||||
return saved_sessions[0][0]
|
return saved_sessions[0][0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -87,12 +90,14 @@ class Memory():
|
|||||||
self.compress()
|
self.compress()
|
||||||
pretty_print("Session recovered successfully", color="success")
|
pretty_print("Session recovered successfully", color="success")
|
||||||
|
|
||||||
def reset(self, memory: list) -> None:
|
def reset(self, memory: list = []) -> None:
|
||||||
|
self.logger.info("Memory reset performed.")
|
||||||
self.memory = memory
|
self.memory = memory
|
||||||
|
|
||||||
def push(self, role: str, content: str) -> int:
|
def push(self, role: str, content: str) -> int:
|
||||||
"""Push a message to the memory."""
|
"""Push a message to the memory."""
|
||||||
if self.memory_compression and role == 'assistant':
|
if self.memory_compression and role == 'assistant':
|
||||||
|
self.logger.info("Compressing memories on message push.")
|
||||||
self.compress()
|
self.compress()
|
||||||
curr_idx = len(self.memory)
|
curr_idx = len(self.memory)
|
||||||
if self.memory[curr_idx-1]['content'] == content:
|
if self.memory[curr_idx-1]['content'] == content:
|
||||||
@@ -101,10 +106,12 @@ class Memory():
|
|||||||
return curr_idx-1
|
return curr_idx-1
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
|
self.logger.info("Memory clear performed.")
|
||||||
self.memory = []
|
self.memory = []
|
||||||
|
|
||||||
def clear_section(self, start: int, end: int) -> None:
|
def clear_section(self, start: int, end: int) -> None:
|
||||||
"""Clear a section of the memory."""
|
"""Clear a section of the memory."""
|
||||||
|
self.logger.info(f"Memory section {start} to {end} cleared.")
|
||||||
self.memory = self.memory[:start] + self.memory[end:]
|
self.memory = self.memory[:start] + self.memory[end:]
|
||||||
|
|
||||||
def get(self) -> list:
|
def get(self) -> list:
|
||||||
@@ -128,6 +135,7 @@ class Memory():
|
|||||||
str: The summarized text
|
str: The summarized text
|
||||||
"""
|
"""
|
||||||
if self.tokenizer is None or self.model is None:
|
if self.tokenizer is None or self.model is None:
|
||||||
|
self.logger.warning("No tokenizer or model to perform summarization.")
|
||||||
return text
|
return text
|
||||||
if len(text) < min_length*1.5:
|
if len(text) < min_length*1.5:
|
||||||
return text
|
return text
|
||||||
@@ -144,6 +152,7 @@ class Memory():
|
|||||||
)
|
)
|
||||||
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
|
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
|
||||||
summary.replace('summary:', '')
|
summary.replace('summary:', '')
|
||||||
|
self.logger.info(f"Memory summarization success from len {len(text)} to {len(summary)}.")
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
#@timer_decorator
|
#@timer_decorator
|
||||||
@@ -160,6 +169,7 @@ class Memory():
|
|||||||
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
|
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
memory = Memory("You are a helpful assistant.",
|
memory = Memory("You are a helpful assistant.",
|
||||||
recover_last_session=False, memory_compression=True)
|
recover_last_session=False, memory_compression=True)
|
||||||
|
|
||||||
|
|||||||
+17
-6
@@ -1,13 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import torch
|
import torch
|
||||||
from typing import List, Tuple, Type, Dict, Tuple
|
from typing import List, Tuple, Type, Dict
|
||||||
|
|
||||||
from transformers import pipeline
|
from transformers import pipeline
|
||||||
from adaptive_classifier import AdaptiveClassifier
|
from adaptive_classifier import AdaptiveClassifier
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
|
|
||||||
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.casual_agent import CasualAgent
|
from sources.agents.casual_agent import CasualAgent
|
||||||
@@ -15,6 +13,7 @@ from sources.agents.planner_agent import FileAgent
|
|||||||
from sources.agents.browser_agent import BrowserAgent
|
from sources.agents.browser_agent import BrowserAgent
|
||||||
from sources.language import LanguageUtility
|
from sources.language import LanguageUtility
|
||||||
from sources.utility import pretty_print, animate_thinking, timer_decorator
|
from sources.utility import pretty_print, animate_thinking, timer_decorator
|
||||||
|
from sources.logger import Logger
|
||||||
|
|
||||||
class AgentRouter:
|
class AgentRouter:
|
||||||
"""
|
"""
|
||||||
@@ -22,6 +21,7 @@ class AgentRouter:
|
|||||||
"""
|
"""
|
||||||
def __init__(self, agents: list):
|
def __init__(self, agents: list):
|
||||||
self.agents = agents
|
self.agents = agents
|
||||||
|
self.logger = Logger("router.log")
|
||||||
self.lang_analysis = LanguageUtility()
|
self.lang_analysis = LanguageUtility()
|
||||||
self.pipelines = self.load_pipelines()
|
self.pipelines = self.load_pipelines()
|
||||||
self.talk_classifier = self.load_llm_router()
|
self.talk_classifier = self.load_llm_router()
|
||||||
@@ -82,6 +82,7 @@ class AgentRouter:
|
|||||||
("search my drive for a file called vacation_photos_2023.jpg.", "LOW"),
|
("search my drive for a file called vacation_photos_2023.jpg.", "LOW"),
|
||||||
("help me organize my desktop files into folders by type.", "LOW"),
|
("help me organize my desktop files into folders by type.", "LOW"),
|
||||||
("write a Python function to sort a list of dictionaries by key", "LOW"),
|
("write a Python function to sort a list of dictionaries by key", "LOW"),
|
||||||
|
("can you search for startup in tokyo?", "LOW"),
|
||||||
("find the latest updates on quantum computing on the web", "LOW"),
|
("find the latest updates on quantum computing on the web", "LOW"),
|
||||||
("check if the folder ‘Work_Projects’ exists on my desktop", "LOW"),
|
("check if the folder ‘Work_Projects’ exists on my desktop", "LOW"),
|
||||||
("create a bash script to monitor CPU usage", "LOW"),
|
("create a bash script to monitor CPU usage", "LOW"),
|
||||||
@@ -161,6 +162,7 @@ class AgentRouter:
|
|||||||
("Search my drive for a file called vacation_photos_2023.jpg.", "files"),
|
("Search my drive for a file called vacation_photos_2023.jpg.", "files"),
|
||||||
("Help me organize my desktop files into folders by type.", "files"),
|
("Help me organize my desktop files into folders by type.", "files"),
|
||||||
("What’s your favorite movie and why?", "talk"),
|
("What’s your favorite movie and why?", "talk"),
|
||||||
|
("what directory are you in ?", "files"),
|
||||||
("Search my drive for a file named budget_2024.xlsx", "files"),
|
("Search my drive for a file named budget_2024.xlsx", "files"),
|
||||||
("Write a Python function to sort a list of dictionaries by key", "code"),
|
("Write a Python function to sort a list of dictionaries by key", "code"),
|
||||||
("Find the latest updates on quantum computing on the web", "web"),
|
("Find the latest updates on quantum computing on the web", "web"),
|
||||||
@@ -307,6 +309,7 @@ class AgentRouter:
|
|||||||
llm_router, confidence_llm_router = result_llm_router[0], result_llm_router[1]
|
llm_router, confidence_llm_router = result_llm_router[0], result_llm_router[1]
|
||||||
final_score_bart = confidence_bart / (confidence_bart + confidence_llm_router)
|
final_score_bart = confidence_bart / (confidence_bart + confidence_llm_router)
|
||||||
final_score_llm = confidence_llm_router / (confidence_bart + confidence_llm_router)
|
final_score_llm = confidence_llm_router / (confidence_bart + confidence_llm_router)
|
||||||
|
self.logger.info(f"Routing Vote: BART: {bart} ({final_score_bart}) LLM-router: {llm_router} ({final_score_llm})")
|
||||||
if log_confidence:
|
if log_confidence:
|
||||||
pretty_print(f"Agent choice -> BART: {bart} ({final_score_bart}) LLM-router: {llm_router} ({final_score_llm})")
|
pretty_print(f"Agent choice -> BART: {bart} ({final_score_bart}) LLM-router: {llm_router} ({final_score_llm})")
|
||||||
return bart if final_score_bart > final_score_llm else llm_router
|
return bart if final_score_bart > final_score_llm else llm_router
|
||||||
@@ -328,12 +331,17 @@ class AgentRouter:
|
|||||||
Returns:
|
Returns:
|
||||||
str: The estimated complexity
|
str: The estimated complexity
|
||||||
"""
|
"""
|
||||||
predictions = self.complexity_classifier.predict(text)
|
try:
|
||||||
|
predictions = self.complexity_classifier.predict(text)
|
||||||
|
except Exception as e:
|
||||||
|
pretty_print(f"Error in estimate_complexity: {str(e)}", color="failure")
|
||||||
|
return "LOW"
|
||||||
predictions = sorted(predictions, key=lambda x: x[1], reverse=True)
|
predictions = sorted(predictions, key=lambda x: x[1], reverse=True)
|
||||||
if len(predictions) == 0:
|
if len(predictions) == 0:
|
||||||
return "LOW"
|
return "LOW"
|
||||||
complexity, confidence = predictions[0][0], predictions[0][1]
|
complexity, confidence = predictions[0][0], predictions[0][1]
|
||||||
if confidence < 0.4:
|
if confidence < 0.4:
|
||||||
|
self.logger.info(f"Low confidence in complexity estimation: {confidence}")
|
||||||
return "LOW"
|
return "LOW"
|
||||||
if complexity == "HIGH" and len(text) < 64:
|
if complexity == "HIGH" and len(text) < 64:
|
||||||
return None # ask for more info
|
return None # ask for more info
|
||||||
@@ -341,8 +349,8 @@ class AgentRouter:
|
|||||||
return "HIGH"
|
return "HIGH"
|
||||||
elif complexity == "LOW":
|
elif complexity == "LOW":
|
||||||
return "LOW"
|
return "LOW"
|
||||||
pretty_print(f"Failed to estimate the complexity of the text. Confidence: {confidence}", color="failure")
|
pretty_print(f"Failed to estimate the complexity of the text.", color="failure")
|
||||||
return None
|
return "LOW"
|
||||||
|
|
||||||
def find_planner_agent(self) -> Agent:
|
def find_planner_agent(self) -> Agent:
|
||||||
"""
|
"""
|
||||||
@@ -354,6 +362,7 @@ class AgentRouter:
|
|||||||
if agent.type == "planner_agent":
|
if agent.type == "planner_agent":
|
||||||
return agent
|
return agent
|
||||||
pretty_print(f"Error finding planner agent. Please add a planner agent to the list of agents.", color="failure")
|
pretty_print(f"Error finding planner agent. Please add a planner agent to the list of agents.", color="failure")
|
||||||
|
self.logger.error("Planner agent not found.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def select_agent(self, text: str) -> Agent:
|
def select_agent(self, text: str) -> Agent:
|
||||||
@@ -386,9 +395,11 @@ class AgentRouter:
|
|||||||
pretty_print(f"Selected agent: {agent.agent_name} (roles: {agent.role[lang]})", color="warning")
|
pretty_print(f"Selected agent: {agent.agent_name} (roles: {agent.role[lang]})", color="warning")
|
||||||
return agent
|
return agent
|
||||||
pretty_print(f"Error choosing agent.", color="failure")
|
pretty_print(f"Error choosing agent.", color="failure")
|
||||||
|
self.logger.error("No agent selected.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
agents = [
|
agents = [
|
||||||
CasualAgent("jarvis", "../prompts/base/casual_agent.txt", None),
|
CasualAgent("jarvis", "../prompts/base/casual_agent.txt", None),
|
||||||
BrowserAgent("browser", "../prompts/base/planner_agent.txt", None),
|
BrowserAgent("browser", "../prompts/base/planner_agent.txt", None),
|
||||||
|
|||||||
+13
-15
@@ -1,18 +1,15 @@
|
|||||||
import os
|
import os, sys
|
||||||
import re
|
import re
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
from sys import modules
|
from sys import modules
|
||||||
from typing import List, Tuple, Type, Dict, Tuple
|
from typing import List, Tuple, Type, Dict
|
||||||
|
|
||||||
from kokoro import KPipeline
|
from kokoro import KPipeline
|
||||||
from IPython.display import display, Audio
|
from IPython.display import display, Audio
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
|
|
||||||
if __name__ == "__main__":
|
from sources.utility import pretty_print, animate_thinking
|
||||||
from utility import pretty_print, animate_thinking
|
|
||||||
else:
|
|
||||||
from sources.utility import pretty_print, animate_thinking
|
|
||||||
|
|
||||||
class Speech():
|
class Speech():
|
||||||
"""
|
"""
|
||||||
@@ -47,22 +44,22 @@ class Speech():
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|
||||||
def speak(self, sentence: str, voice_number: int = 1):
|
def speak(self, sentence: str, voice_idx: int = 1):
|
||||||
"""
|
"""
|
||||||
Convert text to speech using an AI model and play the audio.
|
Convert text to speech using an AI model and play the audio.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sentence (str): The text to convert to speech. Will be pre-processed.
|
sentence (str): The text to convert to speech. Will be pre-processed.
|
||||||
voice_number (int, optional): Index of the voice to use from the voice map.
|
voice_idx (int, optional): Index of the voice to use from the voice map.
|
||||||
"""
|
"""
|
||||||
if not self.pipeline:
|
if not self.pipeline:
|
||||||
return
|
return
|
||||||
if voice_number >= len(self.voice_map[self.language]) or voice_number < 0:
|
if voice_idx >= len(self.voice_map[self.language]):
|
||||||
pretty_print("Invalid voice number, using default voice", color="error")
|
pretty_print("Invalid voice number, using default voice", color="error")
|
||||||
voice_number = 0
|
voice_idx = 0
|
||||||
sentence = self.clean_sentence(sentence)
|
sentence = self.clean_sentence(sentence)
|
||||||
audio_file = f"{self.voice_folder}/sample_{self.voice_map[self.language][voice_number]}.wav"
|
audio_file = f"{self.voice_folder}/sample_{self.voice_map[self.language][voice_idx]}.wav"
|
||||||
self.voice = self.voice_map[self.language][voice_number]
|
self.voice = self.voice_map[self.language][voice_idx]
|
||||||
generator = self.pipeline(
|
generator = self.pipeline(
|
||||||
sentence, voice=self.voice,
|
sentence, voice=self.voice,
|
||||||
speed=self.speed, split_pattern=r'\n+'
|
speed=self.speed, split_pattern=r'\n+'
|
||||||
@@ -143,6 +140,7 @@ class Speech():
|
|||||||
return sentence
|
return sentence
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
speech = Speech()
|
speech = Speech()
|
||||||
tosay_en = """
|
tosay_en = """
|
||||||
I looked up recent news using the website https://www.theguardian.com/world
|
I looked up recent news using the website https://www.theguardian.com/world
|
||||||
@@ -154,8 +152,8 @@ if __name__ == "__main__":
|
|||||||
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world
|
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world
|
||||||
"""
|
"""
|
||||||
spk = Speech(enable=True, language="en", voice_idx=0)
|
spk = Speech(enable=True, language="en", voice_idx=0)
|
||||||
spk.speak(tosay_en)
|
spk.speak(tosay_en, voice_idx=0)
|
||||||
spk = Speech(enable=True, language="fr", voice_idx=0)
|
spk = Speech(enable=True, language="fr", voice_idx=0)
|
||||||
spk.speak(tosay_fr)
|
spk.speak(tosay_fr)
|
||||||
spk = Speech(enable=True, language="zh", voice_idx=0)
|
#spk = Speech(enable=True, language="zh", voice_idx=0)
|
||||||
spk.speak(tosay_zh)
|
#spk.speak(tosay_zh)
|
||||||
+6
-10
@@ -2,11 +2,13 @@
|
|||||||
"""
|
"""
|
||||||
define a generic tool class, any tool can be used by the agent.
|
define a generic tool class, any tool can be used by the agent.
|
||||||
|
|
||||||
A tool can be used by deepseek like so:
|
A tool can be used by a llm like so:
|
||||||
```<tool name>
|
```<tool name>
|
||||||
<code or query to execute>
|
<code or query to execute>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
we call these "blocks".
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```python
|
```python
|
||||||
print("Hello world")
|
print("Hello world")
|
||||||
@@ -40,9 +42,7 @@ class Tools():
|
|||||||
return self.current_dir
|
return self.current_dir
|
||||||
|
|
||||||
def check_config_dir_validity(self):
|
def check_config_dir_validity(self):
|
||||||
"""
|
"""Check if the config directory is valid."""
|
||||||
Check if the config directory is valid.
|
|
||||||
"""
|
|
||||||
path = self.config['MAIN']['work_dir']
|
path = self.config['MAIN']['work_dir']
|
||||||
if path == "":
|
if path == "":
|
||||||
print("WARNING: Work directory not set in config.ini")
|
print("WARNING: Work directory not set in config.ini")
|
||||||
@@ -56,15 +56,11 @@ class Tools():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def config_exists(self):
|
def config_exists(self):
|
||||||
"""
|
"""Check if the config file exists."""
|
||||||
Check if the config file exists.
|
|
||||||
"""
|
|
||||||
return os.path.exists('./config.ini')
|
return os.path.exists('./config.ini')
|
||||||
|
|
||||||
def create_work_dir(self):
|
def create_work_dir(self):
|
||||||
"""
|
"""Create the work directory if it does not exist."""
|
||||||
Create the work directory if it does not exist.
|
|
||||||
"""
|
|
||||||
default_path = os.path.dirname(os.getcwd())
|
default_path = os.path.dirname(os.getcwd())
|
||||||
if self.config_exists():
|
if self.config_exists():
|
||||||
self.config.read('./config.ini')
|
self.config.read('./config.ini')
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Add project root to Python path
|
||||||
|
from sources.agents.browser_agent import BrowserAgent
|
||||||
|
|
||||||
|
class TestBrowserAgentParsing(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Initialize a basic BrowserAgent instance for testing
|
||||||
|
self.agent = BrowserAgent(
|
||||||
|
name="TestAgent",
|
||||||
|
prompt_path="../prompts/base/browser_agent.txt",
|
||||||
|
provider=None
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_extract_links(self):
|
||||||
|
# Test various link formats
|
||||||
|
test_text = """
|
||||||
|
Check this out: https://example.com, and www.google.com!
|
||||||
|
Also try https://test.org/about?page=1.
|
||||||
|
"""
|
||||||
|
expected = [
|
||||||
|
"https://example.com",
|
||||||
|
"www.google.com",
|
||||||
|
"https://test.org/about?page=1"
|
||||||
|
]
|
||||||
|
result = self.agent.extract_links(test_text)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_extract_form(self):
|
||||||
|
# Test form extraction
|
||||||
|
test_text = """
|
||||||
|
Fill this: [username](john) and [password](secret123)
|
||||||
|
Not a form: [random]text
|
||||||
|
"""
|
||||||
|
expected = ["[username](john)", "[password](secret123)"]
|
||||||
|
result = self.agent.extract_form(test_text)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_clean_links(self):
|
||||||
|
# Test link cleaning
|
||||||
|
test_links = [
|
||||||
|
"https://example.com.",
|
||||||
|
"www.test.com,",
|
||||||
|
"https://clean.org!",
|
||||||
|
"https://good.com"
|
||||||
|
]
|
||||||
|
expected = [
|
||||||
|
"https://example.com",
|
||||||
|
"www.test.com",
|
||||||
|
"https://clean.org",
|
||||||
|
"https://good.com"
|
||||||
|
]
|
||||||
|
result = self.agent.clean_links(test_links)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_parse_answer(self):
|
||||||
|
# Test parsing answer with notes and links
|
||||||
|
test_text = """
|
||||||
|
Here's some info
|
||||||
|
Note: This is important. We are doing test it's very cool.
|
||||||
|
action:
|
||||||
|
i wanna navigate to https://test.com
|
||||||
|
"""
|
||||||
|
self.agent.parse_answer(test_text)
|
||||||
|
self.assertEqual(self.agent.notes[0], "Note: This is important. We are doing test it's very cool.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user