diff --git a/README.md b/README.md index 848c88c..e2d50a0 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,25 @@ English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Franç # AgenticSeek: Private, Local Manus Alternative. -*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Powered by advanced reasoning models like DeepSeek R1, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.* +*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.* [![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) [![GitHub stars](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers) +### Why AgenticSeek ? + +* 🔒 Fully Local & Private - Everything runs on your machine — no cloud, no data sharing. Your files, conversations, and searches stay private. + +* 🌐 Smart Web Browsing - AgenticSeek can browse the internet by itself — search, read, extract info, fill web form — all hands-free. + +* 💻 Autonomous Coding Assistant - Need code? It can write, debug, and run programs in Python, C, Go, Java, and more — all without supervision. + +* 🧠 Smart Agent Selection - You ask, it figures out the best agent for the job automatically. Like having a team of experts ready to help. + +* 📋 Plans & Executes Complex Tasks - From trip planning to complex projects — it can split big tasks into steps and get things done using multiple AI agents. + +* 🎙️ Voice-Enabled - Clean, fast, futuristic voice and speech to text allowing you to talk to it like it's your personal AI from a sci-fi movie + +### **Demo** > *Plan a 3 days solo trip to Budapest, find me a list of attractions and hostels, save everything in a CSV file* @@ -19,20 +34,6 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 > 🛠️ **Work in Progress** – Looking for contributors! -### *Capabilities* - -- **100% Local**: *No cloud, runs on your hardware. Your data stays yours.* - -- **Autonomous Web Browsing**: *Autonomous web navigation.* - -- **Autonomous Coding**: *Can write, debug, and run code in Python, C, Golang, Java...* - -- **Agent routing**: *Automatically picks the right agent for the job.* - -- **Planning**: *For complex tasks, spins up multiple agents to plan and execute.* - -- **Memory**: *Efficient memory and sessions management.* - ## Installation Make sure you have chrome driver, docker and python3.10 (or newer) installed. @@ -172,8 +173,6 @@ python3 cli.py **Options 2:** Run with the Web interface. -Note: Currently we advice you run the CLI instead. Web interface is an active work in progress. - Start the backend. ```sh @@ -182,8 +181,6 @@ python3 api.py Go to `http://localhost:3000/` and you should see the web interface. -Please note that the Web interface doesn't stream messages at the moment. - --- ## Usage diff --git a/api.py b/api.py index 862758f..18b971b 100755 --- a/api.py +++ b/api.py @@ -140,7 +140,7 @@ async def get_latest_answer(): "answer": interaction.current_agent.last_answer, "agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None", "success": interaction.current_agent.success, - "blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.current_agent.get_blocks_result())} if interaction.current_agent else {}, + "blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {}, "status": interaction.current_agent.get_status_message if interaction.current_agent else "No status available", "uid": uid } diff --git a/prompts/base/planner_agent.txt b/prompts/base/planner_agent.txt index 92ecd4b..66ed644 100644 --- a/prompts/base/planner_agent.txt +++ b/prompts/base/planner_agent.txt @@ -63,6 +63,7 @@ You: Sure, here is the plan: Rules: - Do not write code. You are a planning agent. +- If you don't know of a concept, use a web agent. - Put your plan in a json with the key "plan". - Give clear, detailled order to each agent and how their task relate to the previous task (if any). - You might use a file agent before code agent to setup a project properly. specify folder name. @@ -75,7 +76,6 @@ Rules: - The file agent can only conduct one action at the time. successive file agent could be needed. - Tell agent to execute without question. - Only use web agent for finding necessary informations. -- If a task might require user email (eg: api services), do not write plan instead ask for user email. - Do not search for tutorial. - Make sure json is within ```json tag - One step, one agent. \ No newline at end of file diff --git a/prompts/jarvis/planner_agent.txt b/prompts/jarvis/planner_agent.txt index b32e6d7..0fc1178 100644 --- a/prompts/jarvis/planner_agent.txt +++ b/prompts/jarvis/planner_agent.txt @@ -75,5 +75,4 @@ Rules: - The file agent can only conduct one action at the time. successive file agent could be needed. - Tell agent to execute without question. - Only use web agent for finding necessary informations. -- If a task might require user email (eg: api services), do not write plan instead ask for user email. - Do not search for tutorial. \ No newline at end of file diff --git a/sources/agents/agent.py b/sources/agents/agent.py index 0573dc5..4e10c59 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -159,6 +159,23 @@ class Agent(): def get_last_tool_type(self) -> str: return self.blocks_result[-1].tool_type if len(self.blocks_result) > 0 else None + + def raw_answer_blocks(self, answer: str) -> str: + """ + Return the answer with all the blocks inserted, as text. + """ + if self.last_answer is None: + return + raw = "" + lines = self.last_answer.split("\n") + for line in lines: + if "block:" in line: + block_idx = int(line.split(":")[1]) + if block_idx < len(self.blocks_result): + raw += self.blocks_result[block_idx].__str__() + else: + raw += line + "\n" + return raw def show_answer(self): """ @@ -175,7 +192,6 @@ class Agent(): self.blocks_result[block_idx].show() else: pretty_print(line, color="output") - self.blocks_result = [] def remove_blocks(self, text: str) -> str: """ @@ -197,6 +213,14 @@ class Agent(): post_lines.append(f"block:{block_idx}") block_idx += 1 return "\n".join(post_lines) + + def show_block(self, block: str) -> None: + """ + Show the block in a pretty way. + """ + pretty_print('▂'*64, color="status") + pretty_print(block, color="code") + pretty_print('▂'*64, color="status") def execute_modules(self, answer: str) -> Tuple[bool, str]: """ @@ -215,6 +239,7 @@ class Agent(): if blocks != None: for block in blocks: + self.show_block(block) output = tool.execute([block]) feedback = tool.interpreter_feedback(output) # tool interpreter feedback success = not tool.execution_failure_check(output) @@ -226,5 +251,4 @@ class Agent(): self.memory.push('user', feedback) if save_path != None: tool.save_block(blocks, save_path) - self.blocks_result = self.blocks_result return True, feedback diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index dd133ab..8a7f8b4 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -56,6 +56,7 @@ class CoderAgent(Agent): self.last_answer = answer await asyncio.sleep(0) break + self.show_answer() animate_thinking("Executing code...", color="status") self.status_message = "Executing code..." exec_success, _ = self.execute_modules(answer) @@ -67,7 +68,6 @@ class CoderAgent(Agent): pretty_print("Execution failure", color="failure") pretty_print("Correcting code...", color="status") self.status_message = "Correcting code..." - self.show_answer() attempt += 1 self.status_message = "Ready" if attempt == max_attempts: diff --git a/sources/agents/planner_agent.py b/sources/agents/planner_agent.py index 47da48d..ada9e94 100644 --- a/sources/agents/planner_agent.py +++ b/sources/agents/planner_agent.py @@ -69,6 +69,9 @@ class PlannerAgent(Agent): line_json = json.loads(block) if 'plan' in line_json: for task in line_json['plan']: + if task['agent'].lower() not in [ag_name.lower() for ag_name in self.agents.keys()]: + pretty_print(f"Agent {task['agent']} does not exist.", color="warning") + return [] agent = { 'agent': task['agent'], 'id': task['id'], @@ -139,7 +142,7 @@ class PlannerAgent(Agent): return [] agents_tasks = self.parse_agent_tasks(answer) if agents_tasks == []: - prompt = f"Failed to parse the tasks. Please make a plan within ```json.\n" + prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n" pretty_print("Failed to make plan. Retrying...", color="warning") continue self.show_plan(agents_tasks, answer) @@ -160,7 +163,10 @@ 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") - next_task = agents_tasks[int(id)][0] + if int(id) == 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]}." #if success: # return agents_tasks # we only update the plan if last task failed, for now update_prompt = f""" @@ -169,10 +175,11 @@ class PlannerAgent(Agent): The last agent working on task: {id}, did the following work: {last_agent_work} Agent {id} work was a {tool_success_str} according to system interpreter. - The agent {id} about to work on task: {next_task} + {next_task} Is the work done for task {id} leading to sucess or failure ? Did an agent fail with a task? If agent work was good: answer "NO_UPDATE" If agent work is leading to failure: update the plan. + If a task failed add a task to try again or recover from failure. You might have near identical task twice. plan should be within ```json like before. You need to rewrite the whole plan, but only change the tasks after task {id}. Keep the plan as short as the original one if possible. Do not change past tasks. @@ -196,10 +203,15 @@ class PlannerAgent(Agent): self.status_message = f"Starting task {task['task']}..." agent_prompt = self.make_prompt(task['task'], required_infos) pretty_print(f"Agent {task['agent']} started working...", color="status") - agent_answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None) + answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None) + self.last_answer = answer + self.blocks_result = self.agents[task['agent'].lower()].blocks_result + agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer) success = self.agents[task['agent'].lower()].get_success self.agents[task['agent'].lower()].show_answer() pretty_print(f"Agent {task['agent']} completed task.", color="status") + # TODO ajouter feedback / agent et code executer + agent_answer += "\nAgent succeeded with task." if success else "\nAgent failed with task (Error detected)." return agent_answer, success def get_work_result_agent(self, task_needs, agents_work_result): @@ -215,6 +227,7 @@ class PlannerAgent(Agent): Tuple[str, str]: The result of the agent process and empty reasoning string. """ agents_tasks = [] + required_infos = None agents_work_result = dict() self.status_message = "Making a plan..." @@ -234,14 +247,12 @@ class PlannerAgent(Agent): if agents_work_result is not None: required_infos = self.get_work_result_agent(task['need'], agents_work_result) try: - self.last_answer, success = await self.start_agent_process(task, required_infos) + answer, success = await self.start_agent_process(task, required_infos) except Exception as e: raise e - agents_work_result[task['id']] = self.last_answer - if i == steps - 1: - break + 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) i += 1 - return self.last_answer, "" \ No newline at end of file + return answer, "" \ No newline at end of file diff --git a/sources/browser.py b/sources/browser.py index e68da01..0351cb9 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -176,7 +176,6 @@ class Browser: ) except TimeoutException: self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'") - return False self.apply_web_safety() self.logger.log(f"Navigated to: {url}") return True diff --git a/sources/interaction.py b/sources/interaction.py index fe52bf4..eec6ff4 100644 --- a/sources/interaction.py +++ b/sources/interaction.py @@ -69,6 +69,15 @@ class Interaction: break return ai_name + def get_last_blocks_result(self) -> List[Dict]: + """Get the last blocks result.""" + if self.current_agent is None: + return [] + blks = [] + for agent in self.agents: + blks.extend(agent.get_blocks_result()) + return blks + def load_last_session(self): """Recover the last session.""" for agent in self.agents: diff --git a/sources/llm_provider.py b/sources/llm_provider.py index eeb371d..a0ecb3e 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -29,6 +29,7 @@ class Provider: "openai": self.openai_fn, "lm-studio": self.lm_studio_fn, "huggingface": self.huggingface_fn, + "google": self.google_fn, "deepseek": self.deepseek_fn, "together": self.together_fn, "dsk_deepseek": self.dsk_deepseek, @@ -36,10 +37,10 @@ class Provider: } self.logger = Logger("provider.log") self.api_key = None - self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together"] + self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google"] if self.provider_name not in self.available_providers: raise ValueError(f"Unknown provider: {provider_name}") - if self.provider_name in self.unsafe_providers: + if self.provider_name in self.unsafe_providers and self.is_local == False: pretty_print("Warning: you are using an API provider. You data will be sent to the cloud.", color="warning") self.api_key = self.get_api_key(self.provider_name) elif self.provider_name != "ollama": @@ -140,7 +141,6 @@ class Provider: raise e return thought - def ollama_fn(self, history, verbose = False): """ Use local ollama server to generate text. @@ -206,6 +206,29 @@ class Provider: return thought except Exception as e: raise Exception(f"OpenAI API error: {str(e)}") from e + + def google_fn(self, history, verbose=False): + """ + Use google gemini to generate text. + """ + base_url = self.server_ip + if self.is_local: + raise Exception("Google Gemini is not available for local use.") + + client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/") + try: + response = client.chat.completions.create( + model=self.model, + messages=history, + ) + if response is None: + raise Exception("Google response is empty.") + thought = response.choices[0].message.content + if verbose: + print(thought) + return thought + except Exception as e: + raise Exception(f"GOOGLE API error: {str(e)}") from e def together_fn(self, history, verbose=False): """ diff --git a/sources/schemas.py b/sources/schemas.py index ad72830..29410ba 100644 --- a/sources/schemas.py +++ b/sources/schemas.py @@ -68,9 +68,8 @@ class executorResult: "success": self.success, "tool_type": self.tool_type } - + def show(self): pretty_print('▂'*64, color="status") - pretty_print(self.block, color="code" if self.success else "failure") - pretty_print('▂'*64, color="status") - pretty_print(self.feedback, color="success" if self.success else "failure") \ No newline at end of file + pretty_print(self.feedback, color="success" if self.success else "failure") + pretty_print('▂'*64, color="status") \ No newline at end of file diff --git a/sources/tools/mcp_finder.py b/sources/tools/mcpFinder.py similarity index 100% rename from sources/tools/mcp_finder.py rename to sources/tools/mcpFinder.py