diff --git a/api.py b/api.py index cb0e2fa..fa82896 100755 --- a/api.py +++ b/api.py @@ -128,6 +128,12 @@ async def is_active(): logger.info("Is active endpoint called") return {"is_active": interaction.is_active} +@api.get("/stop") +async def stop(): + logger.info("Stop endpoint called") + interaction.current_agent.request_stop() + return JSONResponse(status_code=200, content={"status": "stopped"}) + @api.get("/latest_answer") async def get_latest_answer(): global query_resp_history diff --git a/frontend/agentic-seek-front/src/App.js b/frontend/agentic-seek-front/src/App.js index 4293a6a..7f16b9d 100644 --- a/frontend/agentic-seek-front/src/App.js +++ b/frontend/agentic-seek-front/src/App.js @@ -122,6 +122,19 @@ function App() { })); }; + const handleStop = async (e) => { + e.preventDefault(); + checkHealth(); + setIsLoading(false); + setError(null); + try { + const res = await axios.get('http://127.0.0.1:8000/stop'); + setStatus("Requesting stop..."); + } catch (err) { + console.error('Error stopping the agent:', err); + } + } + const handleSubmit = async (e) => { e.preventDefault(); checkHealth(); @@ -214,6 +227,9 @@ function App() { + diff --git a/sources/.screenshots/updated_screen.png b/sources/.screenshots/updated_screen.png new file mode 100644 index 0000000..fc6aa5d Binary files /dev/null and b/sources/.screenshots/updated_screen.png differ diff --git a/sources/agents/agent.py b/sources/agents/agent.py index ca4c079..0e6bfaf 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -46,6 +46,7 @@ class Agent(): self.last_answer = "" self.last_reasoning = "" self.status_message = "Haven't started yet" + self.stop = False self.verbose = verbose self.executor = ThreadPoolExecutor(max_workers=1) @@ -119,6 +120,13 @@ class Agent(): except Exception as e: raise e + def request_stop(self) -> None: + """ + Request the agent to stop. + """ + self.stop = True + self.status_message = "Stopped" + @abstractmethod def process(self, prompt, speech_module) -> str: """ diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index 20dc7c9..3817fb3 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -181,6 +181,7 @@ class BrowserAgent(Agent): animate_thinking("Thinking...", color="status") self.memory.push('user', prompt) answer, reasoning = await self.llm_request() + self.last_reasoning = reasoning if show_reasoning: pretty_print(reasoning, color="failure") pretty_print(answer, color="output") @@ -349,11 +350,13 @@ class BrowserAgent(Agent): self.show_search_results(search_result) prompt = self.make_newsearch_prompt(user_prompt, search_result) unvisited = [None] - while not complete and len(unvisited) > 0: - + while not complete and len(unvisited) > 0 and not self.stop: self.memory.clear() unvisited = self.select_unvisited(search_result) answer, reasoning = await self.llm_decide(prompt, show_reasoning = False) + if self.stop: + pretty_print(f"Requested stop.", color="failure") + break if self.last_answer == answer: prompt = self.stuck_prompt(user_prompt, unvisited) continue @@ -424,4 +427,4 @@ class BrowserAgent(Agent): return answer, reasoning if __name__ == "__main__": - pass \ No newline at end of file + pass diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index f7ba3b9..f75baec 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -51,10 +51,12 @@ class CoderAgent(Agent): self.memory.push('user', prompt) clarify_trigger = "REQUEST_CLARIFICATION" - while attempt < max_attempts: + while attempt < max_attempts and not self.stop: + print("Stopped?", self.stop) animate_thinking("Thinking...", color="status") await self.wait_message(speech_module) answer, reasoning = await self.llm_request() + self.last_reasoning = reasoning if clarify_trigger in answer: self.last_answer = answer await asyncio.sleep(0) diff --git a/sources/agents/file_agent.py b/sources/agents/file_agent.py index 8dbd8e8..d88b898 100644 --- a/sources/agents/file_agent.py +++ b/sources/agents/file_agent.py @@ -28,10 +28,11 @@ class FileAgent(Agent): exec_success = False prompt += f"\nYou must work in directory: {self.work_dir}" self.memory.push('user', prompt) - while exec_success is False: + while exec_success is False and not self.stop: await self.wait_message(speech_module) animate_thinking("Thinking...", color="status") answer, reasoning = await self.llm_request() + self.last_reasoning = reasoning exec_success, _ = self.execute_modules(answer) answer = self.remove_blocks(answer) self.last_answer = answer diff --git a/sources/agents/planner_agent.py b/sources/agents/planner_agent.py index 1a6d089..a588604 100644 --- a/sources/agents/planner_agent.py +++ b/sources/agents/planner_agent.py @@ -83,11 +83,15 @@ class PlannerAgent(Agent): self.logger.warning(f"Agent {task['agent']} does not exist.") pretty_print(f"Agent {task['agent']} does not exist.", color="warning") return [] - agent = { - 'agent': task['agent'], - 'id': task['id'], - 'task': task['task'] - } + try: + agent = { + 'agent': task['agent'], + 'id': task['id'], + 'task': task['task'] + } + except: + self.logger.warning("Missing field in json plan.") + return [] self.logger.info(f"Created agent {task['agent']} with task: {task['task']}") if 'need' in task: self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}") @@ -156,6 +160,7 @@ class PlannerAgent(Agent): return [] agents_tasks = self.parse_agent_tasks(answer) if agents_tasks == []: + self.show_plan(agents_tasks, answer) prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n" pretty_print("Failed to make plan. Retrying...", color="warning") continue @@ -178,7 +183,11 @@ class PlannerAgent(Agent): last_agent_work = agents_work_result[id] tool_success_str = "success" if success else "failure" pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure") - if int(id) == len(agents_tasks): + try: + id_int = int(id) + except Exception as e: + return agents_tasks + if id_int == len(agents_tasks): next_task = "No task follow, this was the last step. If it failed add a task to recover." else: next_task = f"Next task is: {agents_tasks[int(id)][0]}." @@ -258,7 +267,7 @@ class PlannerAgent(Agent): return "Failed to parse the tasks.", "" i = 0 steps = len(agents_tasks) - while i < steps: + while i < steps and not self.stop: task_name, task = agents_tasks[i][0], agents_tasks[i][1] self.status_message = "Starting agents..." pretty_print(f"I will {task_name}.", color="info") @@ -272,6 +281,8 @@ class PlannerAgent(Agent): answer, success = await self.start_agent_process(task, required_infos) except Exception as e: raise e + if self.stop: + pretty_print(f"Requested stop.", color="failure") agents_work_result[task['id']] = answer agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success) steps = len(agents_tasks) diff --git a/sources/browser.py b/sources/browser.py index e33f1df..739097b 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -206,7 +206,11 @@ class Browser: def setup_tabs(self): self.tabs = self.driver.window_handles - self.driver.get("https://www.google.com") + try: + self.driver.get("https://www.google.com") + except Exception as e: + self.logger.log(f"Failed to setup initial tab:" + str(e)) + pass self.screenshot() def switch_control_tab(self): @@ -215,7 +219,11 @@ class Browser: def load_anticatpcha_manually(self): pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning") - self.driver.get(self.anticaptcha) + try: + self.driver.get(self.anticaptcha) + except Exception as e: + self.logger.log(f"Failed to setup initial tab:" + str(e)) + pass def human_move(element): actions = ActionChains(driver) @@ -685,15 +693,17 @@ class Browser: input_elements = self.driver.execute_script(script) if __name__ == "__main__": - driver = create_driver(headless=False, stealth_mode=True) - browser = Browser(driver, anticaptcha_manual_install=False) + driver = create_driver(headless=False, stealth_mode=True, crx_path="../crx/nopecha.crx") + browser = Browser(driver, anticaptcha_manual_install=True) input("press enter to continue") print("AntiCaptcha / Form Test") + browser.go_to("https://www.biorxiv.org/content/10.1101/2025.05.19.654955v1") + time.sleep(55) + browser.go_to("https://www.google.com/recaptcha/api2/demo") browser.go_to("https://bot.sannysoft.com") time.sleep(5) #txt = browser.get_text() - #browser.go_to("https://www.google.com/recaptcha/api2/demo") browser.go_to("https://home.openweathermap.org/users/sign_up") inputs_visible = browser.get_form_inputs() print("inputs:", inputs_visible) diff --git a/sources/llm_provider.py b/sources/llm_provider.py index 263ed07..d12cc72 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -32,11 +32,12 @@ class Provider: "deepseek": self.deepseek_fn, "together": self.together_fn, "dsk_deepseek": self.dsk_deepseek, - "test": self.test_fn + "test": self.test_fn, + "anthropic": self.anthropic_fn } self.logger = Logger("provider.log") self.api_key = None - self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google"] + self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google", "anthropic"] if self.provider_name not in self.available_providers: raise ValueError(f"Unknown provider: {provider_name}") if self.provider_name in self.unsafe_providers and self.is_local == False: @@ -57,6 +58,38 @@ class Provider: exit(1) return api_key + def anthropic_fn(self, history, verbose=False): + """ + Use Anthropic to generate text. + """ + from anthropic import Anthropic + + client = Anthropic(api_key=self.api_key) + system_message = None + messages = [] + for message in history: + clean_message = {'role': message['role'], 'content': message['content']} + if message['role'] == 'system': + system_message = message['content'] + else: + messages.append(clean_message) + + try: + response = client.messages.create( + model=self.model, + max_tokens=1024, + messages=messages, + system=system_message + ) + if response is None: + raise Exception("Anthropic response is empty.") + thought = response.content[0].text + if verbose: + print(thought) + return thought + except Exception as e: + raise Exception(f"Anthropic API error: {str(e)}") from e + def respond(self, history, verbose=True): """ Use the choosen provider to generate text. diff --git a/sources/tools/BashInterpreter.py b/sources/tools/BashInterpreter.py index 9d56584..2793604 100644 --- a/sources/tools/BashInterpreter.py +++ b/sources/tools/BashInterpreter.py @@ -8,7 +8,7 @@ if __name__ == "__main__": # if running as a script for individual testing sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from sources.tools.tools import Tools -from sources.tools.safety import is_unsafe +from sources.tools.safety import is_any_unsafe class BashInterpreter(Tools): """ @@ -43,9 +43,9 @@ class BashInterpreter(Tools): for command in commands: command = f"cd {self.work_dir} && {command}" command = command.replace('\n', '') - if self.safe_mode and is_unsafe(commands): + if self.safe_mode and is_any_unsafe(commands): print(f"Unsafe command rejected: {command}") - return "Unsafe command detected, execution aborted." + return "\nUnsafe command detected, execution aborted." if self.language_bash_attempt(command) and self.allow_language_exec_bash == False: continue try: @@ -100,6 +100,7 @@ class BashInterpreter(Tools): r"not permitted", r"not installed", r"not found", + r"aborted", r"no such", r"too many", r"too few", diff --git a/sources/tools/safety.py b/sources/tools/safety.py index 5009331..42485e7 100644 --- a/sources/tools/safety.py +++ b/sources/tools/safety.py @@ -66,6 +66,15 @@ unsafe_commands_windows = [ "bootcfg" ] +def is_any_unsafe(cmds): + """ + check if any bash command is unsafe. + """ + for cmd in cmds: + if is_unsafe(cmd): + return True + return False + def is_unsafe(cmd): """ check if a bash command is unsafe.