From 983e8f0d4b4a9cf628c81e554cdeab836a6b02bf Mon Sep 17 00:00:00 2001 From: the0807 Date: Wed, 12 Mar 2025 14:33:32 +0900 Subject: [PATCH 1/7] Support OpenAI Reasoning Models (o1, o3-mini) --- app/llm.py | 56 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/app/llm.py b/app/llm.py index 8f62782..90e6f03 100644 --- a/app/llm.py +++ b/app/llm.py @@ -14,6 +14,7 @@ from app.config import LLMSettings, config from app.logger import logger # Assuming a logger is set up in your app from app.schema import Message +REASONING_MODELS = ["o1", "o3-mini"] class LLM: _instances: Dict[str, "LLM"] = {} @@ -133,27 +134,30 @@ class LLM: else: messages = self.format_messages(messages) + params = { + "model": self.model, + "messages": messages, + } + + if self.model in REASONING_MODELS: + params["max_completion_tokens"] = self.max_tokens + else: + params["max_tokens"] = self.max_tokens + params["temperature"] = temperature or self.temperature + if not stream: # Non-streaming request - response = await self.client.chat.completions.create( - model=self.model, - messages=messages, - max_tokens=self.max_tokens, - temperature=temperature or self.temperature, - stream=False, - ) + params["stream"] = False + + response = await self.client.chat.completions.create(**params) + if not response.choices or not response.choices[0].message.content: raise ValueError("Empty or invalid response from LLM") return response.choices[0].message.content # Streaming request - response = await self.client.chat.completions.create( - model=self.model, - messages=messages, - max_tokens=self.max_tokens, - temperature=temperature or self.temperature, - stream=True, - ) + params["stream"] = True + response = await self.client.chat.completions.create(**params) collected_messages = [] async for chunk in response: @@ -230,16 +234,22 @@ class LLM: raise ValueError("Each tool must be a dict with 'type' field") # Set up the completion request - response = await self.client.chat.completions.create( - model=self.model, - messages=messages, - temperature=temperature or self.temperature, - max_tokens=self.max_tokens, - tools=tools, - tool_choice=tool_choice, - timeout=timeout, + params = { + "model": self.model, + "messages": messages, + "tools": tools, + "tool_choice": tool_choice, + "timeout": timeout, **kwargs, - ) + } + + if self.model in REASONING_MODELS: + params["max_completion_tokens"] = self.max_tokens + else: + params["max_tokens"] = self.max_tokens + params["temperature"] = temperature or self.temperature + + response = await self.client.chat.completions.create(**params) # Check if response is valid if not response.choices or not response.choices[0].message: From ed4b78dc37bd31a59d0831f2054527a1845e2e9d Mon Sep 17 00:00:00 2001 From: 836304831 <836304831@qq.com> Date: Wed, 12 Mar 2025 23:33:37 +0800 Subject: [PATCH 2/7] update python_execute safe --- app/tool/python_execute.py | 73 +++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/app/tool/python_execute.py b/app/tool/python_execute.py index 88e1aab..e9c8140 100644 --- a/app/tool/python_execute.py +++ b/app/tool/python_execute.py @@ -1,4 +1,6 @@ -import threading +import sys +from io import StringIO +import multiprocessing from typing import Dict from app.tool.base import BaseTool @@ -20,6 +22,20 @@ class PythonExecute(BaseTool): "required": ["code"], } + def _run_code(self, code: str, result_dict: dict, safe_globals: dict) -> None: + original_stdout = sys.stdout + try: + output_buffer = StringIO() + sys.stdout = output_buffer + exec(code, safe_globals, safe_globals) + result_dict["observation"] = output_buffer.getvalue() + result_dict["success"] = True + except Exception as e: + result_dict["observation"] = str(e) + result_dict["success"] = False + finally: + sys.stdout = original_stdout + async def execute( self, code: str, @@ -35,36 +51,29 @@ class PythonExecute(BaseTool): Returns: Dict: Contains 'output' with execution output or error message and 'success' status. """ - result = {"observation": ""} - def run_code(): - try: - safe_globals = {"__builtins__": dict(__builtins__)} + with multiprocessing.Manager() as manager: + result = manager.dict({ + "observation": "", + "success": False + }) + if isinstance(__builtins__, dict): + safe_globals = {"__builtins__": __builtins__} + else: + safe_globals = {"__builtins__": __builtins__.__dict__.copy()} + proc = multiprocessing.Process( + target=self._run_code, + args=(code, result, safe_globals) + ) + proc.start() + proc.join(timeout) - import sys - from io import StringIO - - output_buffer = StringIO() - sys.stdout = output_buffer - - exec(code, safe_globals, {}) - - sys.stdout = sys.__stdout__ - - result["observation"] = output_buffer.getvalue() - - except Exception as e: - result["observation"] = str(e) - result["success"] = False - - thread = threading.Thread(target=run_code) - thread.start() - thread.join(timeout) - - if thread.is_alive(): - return { - "observation": f"Execution timeout after {timeout} seconds", - "success": False, - } - - return result + # timeout process + if proc.is_alive(): + proc.terminate() + proc.join(1) + return { + "observation": f"Execution timeout after {timeout} seconds", + "success": False, + } + return dict(result) From 89c9d904db1c2c873b3b0428d5f2e99737d9bddb Mon Sep 17 00:00:00 2001 From: xRay <3864998@qq.com> Date: Fri, 14 Mar 2025 09:46:46 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=B0=86=E5=B7=A5=E5=85=B7=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E4=BB=8E=20ToolChoice.REQUIRED=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=B8=BA=20ToolChoice.AUTO=EF=BC=8C=E4=BB=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A7=84=E5=88=92=E4=BB=A3=E7=90=86=E5=92=8C=E8=A7=84=E5=88=92?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E7=9A=84=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/agent/planning.py | 2 +- app/flow/planning.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/agent/planning.py b/app/agent/planning.py index cbd15a0..8cc2be8 100644 --- a/app/agent/planning.py +++ b/app/agent/planning.py @@ -212,7 +212,7 @@ class PlanningAgent(ToolCallAgent): messages=messages, system_msgs=[Message.system_message(self.system_prompt)], tools=self.available_tools.to_params(), - tool_choice=ToolChoice.REQUIRED, + tool_choice=ToolChoice.AUTO, ) assistant_msg = Message.from_tool_calls( content=response.content, tool_calls=response.tool_calls diff --git a/app/flow/planning.py b/app/flow/planning.py index a12bbe4..55ec5c9 100644 --- a/app/flow/planning.py +++ b/app/flow/planning.py @@ -124,7 +124,7 @@ class PlanningFlow(BaseFlow): messages=[user_message], system_msgs=[system_message], tools=[self.planning_tool.to_param()], - tool_choice=ToolChoice.REQUIRED, + tool_choice=ToolChoice.AUTO, ) # Process tool calls if present From 9c7834eff2c42da0c871caec05cfdace1cf29de5 Mon Sep 17 00:00:00 2001 From: liangxinbing <1580466765@qq.com> Date: Fri, 14 Mar 2025 12:20:59 +0800 Subject: [PATCH 4/7] update readme; format code; update config.example.toml --- README.md | 2 +- README_ja.md | 2 +- README_ko.md | 2 +- README_zh.md | 4 ++-- app/agent/base.py | 4 ++-- app/agent/manus.py | 3 +-- app/agent/planning.py | 4 ++-- app/agent/toolcall.py | 7 +++---- app/config.py | 6 ++++-- app/llm.py | 14 +++++++++++--- app/schema.py | 15 ++++++++++++--- app/tool/python_execute.py | 10 +++------- app/tool/search/__init__.py | 4 ++-- app/tool/search/baidu_search.py | 4 ++-- app/tool/search/base.py | 6 ++++-- app/tool/search/duckduckgo_search.py | 4 ++-- app/tool/search/google_search.py | 7 ++++--- app/tool/terminal.py | 22 +++++++++++----------- app/tool/web_search.py | 12 +++++++++--- config/config.example.toml | 20 +++++++++++--------- 20 files changed, 88 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index ee33f75..ae93a47 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Join our networking group on Feishu and share your experience with other develop Thanks to [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) and [browser-use](https://github.com/browser-use/browser-use) for providing basic support for this project! -Additionally, we are grateful to [AAAJ](https://github.com/metauto-ai/agent-as-a-judge), [MetaGPT](https://github.com/geekan/MetaGPT) and [OpenHands](https://github.com/All-Hands-AI/OpenHands). +Additionally, we are grateful to [AAAJ](https://github.com/metauto-ai/agent-as-a-judge), [MetaGPT](https://github.com/geekan/MetaGPT), [OpenHands](https://github.com/All-Hands-AI/OpenHands) and [SWE-agent](https://github.com/SWE-agent/SWE-agent). OpenManus is built by contributors from MetaGPT. Huge thanks to this agent community! diff --git a/README_ja.md b/README_ja.md index 668d9e3..2dd84d5 100644 --- a/README_ja.md +++ b/README_ja.md @@ -144,7 +144,7 @@ Feishuのネットワーキンググループに参加して、他の開発者 このプロジェクトの基本的なサポートを提供してくれた[anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) と[browser-use](https://github.com/browser-use/browser-use)に感謝します! -さらに、[AAAJ](https://github.com/metauto-ai/agent-as-a-judge)、[MetaGPT](https://github.com/geekan/MetaGPT)、[OpenHands](https://github.com/All-Hands-AI/OpenHands)にも感謝します。 +さらに、[AAAJ](https://github.com/metauto-ai/agent-as-a-judge)、[MetaGPT](https://github.com/geekan/MetaGPT)、[OpenHands](https://github.com/All-Hands-AI/OpenHands)、[SWE-agent](https://github.com/SWE-agent/SWE-agent)にも感謝します。 OpenManusはMetaGPTのコントリビューターによって構築されました。このエージェントコミュニティに大きな感謝を! diff --git a/README_ko.md b/README_ko.md index 5cefd84..379363e 100644 --- a/README_ko.md +++ b/README_ko.md @@ -144,7 +144,7 @@ Feishu 네트워킹 그룹에 참여하여 다른 개발자들과 경험을 공 이 프로젝트에 기본적인 지원을 제공해 주신 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)와 [browser-use](https://github.com/browser-use/browser-use)에게 감사드립니다! -또한, [AAAJ](https://github.com/metauto-ai/agent-as-a-judge), [MetaGPT](https://github.com/geekan/MetaGPT), [OpenHands](https://github.com/All-Hands-AI/OpenHands)에 깊은 감사를 드립니다. +또한, [AAAJ](https://github.com/metauto-ai/agent-as-a-judge), [MetaGPT](https://github.com/geekan/MetaGPT), [OpenHands](https://github.com/All-Hands-AI/OpenHands), [SWE-agent](https://github.com/SWE-agent/SWE-agent)에 깊은 감사를 드립니다. OpenManus는 MetaGPT 기여자들에 의해 개발되었습니다. 이 에이전트 커뮤니티에 깊은 감사를 전합니다! diff --git a/README_zh.md b/README_zh.md index 28f6749..ea7f904 100644 --- a/README_zh.md +++ b/README_zh.md @@ -119,7 +119,7 @@ python main.py 然后通过终端输入你的创意! -如需体验开发中版本,可运行: +如需体验不稳定的开发版本,可运行: ```bash python run_flow.py @@ -148,7 +148,7 @@ python run_flow.py 特别感谢 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) 和 [browser-use](https://github.com/browser-use/browser-use) 为本项目提供的基础支持! -此外,我们感谢 [AAAJ](https://github.com/metauto-ai/agent-as-a-judge),[MetaGPT](https://github.com/geekan/MetaGPT) 和 [OpenHands](https://github.com/All-Hands-AI/OpenHands). +此外,我们感谢 [AAAJ](https://github.com/metauto-ai/agent-as-a-judge),[MetaGPT](https://github.com/geekan/MetaGPT),[OpenHands](https://github.com/All-Hands-AI/OpenHands) 和 [SWE-agent](https://github.com/SWE-agent/SWE-agent). OpenManus 由 MetaGPT 社区的贡献者共同构建,感谢这个充满活力的智能体开发者社区! diff --git a/app/agent/base.py b/app/agent/base.py index 3830365..fa3db30 100644 --- a/app/agent/base.py +++ b/app/agent/base.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, Field, model_validator from app.llm import LLM from app.logger import logger -from app.schema import AgentState, Memory, Message, ROLE_TYPE +from app.schema import ROLE_TYPE, AgentState, Memory, Message class BaseAgent(BaseModel, ABC): @@ -82,7 +82,7 @@ class BaseAgent(BaseModel, ABC): def update_memory( self, - role: ROLE_TYPE, # type: ignore + role: ROLE_TYPE, # type: ignore content: str, **kwargs, ) -> None: diff --git a/app/agent/manus.py b/app/agent/manus.py index fdf0a10..6c2c2e5 100644 --- a/app/agent/manus.py +++ b/app/agent/manus.py @@ -7,9 +7,8 @@ from app.prompt.manus import NEXT_STEP_PROMPT, SYSTEM_PROMPT from app.tool import Terminate, ToolCollection from app.tool.browser_use_tool import BrowserUseTool from app.tool.file_saver import FileSaver -from app.tool.web_search import WebSearch from app.tool.python_execute import PythonExecute -from app.config import config +from app.tool.web_search import WebSearch class Manus(ToolCallAgent): diff --git a/app/agent/planning.py b/app/agent/planning.py index 8cc2be8..7e98912 100644 --- a/app/agent/planning.py +++ b/app/agent/planning.py @@ -6,7 +6,7 @@ from pydantic import Field, model_validator from app.agent.toolcall import ToolCallAgent from app.logger import logger from app.prompt.planning import NEXT_STEP_PROMPT, PLANNING_SYSTEM_PROMPT -from app.schema import Message, TOOL_CHOICE_TYPE, ToolCall, ToolChoice +from app.schema import TOOL_CHOICE_TYPE, Message, ToolCall, ToolChoice from app.tool import PlanningTool, Terminate, ToolCollection @@ -27,7 +27,7 @@ class PlanningAgent(ToolCallAgent): available_tools: ToolCollection = Field( default_factory=lambda: ToolCollection(PlanningTool(), Terminate()) ) - tool_choices: TOOL_CHOICE_TYPE = ToolChoice.AUTO # type: ignore + tool_choices: TOOL_CHOICE_TYPE = ToolChoice.AUTO # type: ignore special_tool_names: List[str] = Field(default_factory=lambda: [Terminate().name]) tool_calls: List[ToolCall] = Field(default_factory=list) diff --git a/app/agent/toolcall.py b/app/agent/toolcall.py index 1f04784..ecf0bb4 100644 --- a/app/agent/toolcall.py +++ b/app/agent/toolcall.py @@ -1,13 +1,12 @@ import json - -from typing import Any, List, Literal, Optional, Union +from typing import Any, List, Optional, Union from pydantic import Field from app.agent.react import ReActAgent from app.logger import logger from app.prompt.toolcall import NEXT_STEP_PROMPT, SYSTEM_PROMPT -from app.schema import AgentState, Message, ToolCall, TOOL_CHOICE_TYPE, ToolChoice +from app.schema import TOOL_CHOICE_TYPE, AgentState, Message, ToolCall, ToolChoice from app.tool import CreateChatCompletion, Terminate, ToolCollection @@ -26,7 +25,7 @@ class ToolCallAgent(ReActAgent): available_tools: ToolCollection = ToolCollection( CreateChatCompletion(), Terminate() ) - tool_choices: TOOL_CHOICE_TYPE = ToolChoice.AUTO # type: ignore + tool_choices: TOOL_CHOICE_TYPE = ToolChoice.AUTO # type: ignore special_tool_names: List[str] = Field(default_factory=lambda: [Terminate().name]) tool_calls: List[ToolCall] = Field(default_factory=list) diff --git a/app/config.py b/app/config.py index 8fd8bd7..0a267d7 100644 --- a/app/config.py +++ b/app/config.py @@ -30,8 +30,10 @@ class ProxySettings(BaseModel): username: Optional[str] = Field(None, description="Proxy username") password: Optional[str] = Field(None, description="Proxy password") + class SearchSettings(BaseModel): - engine: str = Field(default='Google', description="Search engine the llm to use") + engine: str = Field(default="Google", description="Search engine the llm to use") + class BrowserSettings(BaseModel): headless: bool = Field(False, description="Whether to run browser in headless mode") @@ -180,7 +182,7 @@ class Config: @property def browser_config(self) -> Optional[BrowserSettings]: return self._config.browser_config - + @property def search_config(self) -> Optional[SearchSettings]: return self._config.search_config diff --git a/app/llm.py b/app/llm.py index 3314062..8c085ae 100644 --- a/app/llm.py +++ b/app/llm.py @@ -12,10 +12,18 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential from app.config import LLMSettings, config from app.logger import logger # Assuming a logger is set up in your app -from app.schema import Message, TOOL_CHOICE_TYPE, ROLE_VALUES, TOOL_CHOICE_VALUES, ToolChoice +from app.schema import ( + ROLE_VALUES, + TOOL_CHOICE_TYPE, + TOOL_CHOICE_VALUES, + Message, + ToolChoice, +) + REASONING_MODELS = ["o1", "o3-mini"] + class LLM: _instances: Dict[str, "LLM"] = {} @@ -140,7 +148,7 @@ class LLM: } if self.model in REASONING_MODELS: - params["max_completion_tokens"] = self.max_tokens + params["max_completion_tokens"] = self.max_tokens else: params["max_tokens"] = self.max_tokens params["temperature"] = temperature or self.temperature @@ -191,7 +199,7 @@ class LLM: system_msgs: Optional[List[Union[dict, Message]]] = None, timeout: int = 300, tools: Optional[List[dict]] = None, - tool_choice: TOOL_CHOICE_TYPE = ToolChoice.AUTO, # type: ignore + tool_choice: TOOL_CHOICE_TYPE = ToolChoice.AUTO, # type: ignore temperature: Optional[float] = None, **kwargs, ): diff --git a/app/schema.py b/app/schema.py index 30ccf6c..fb89c3c 100644 --- a/app/schema.py +++ b/app/schema.py @@ -3,25 +3,32 @@ from typing import Any, List, Literal, Optional, Union from pydantic import BaseModel, Field + class Role(str, Enum): """Message role options""" + SYSTEM = "system" USER = "user" - ASSISTANT = "assistant" + ASSISTANT = "assistant" TOOL = "tool" + ROLE_VALUES = tuple(role.value for role in Role) ROLE_TYPE = Literal[ROLE_VALUES] # type: ignore + class ToolChoice(str, Enum): """Tool choice options""" + NONE = "none" AUTO = "auto" REQUIRED = "required" + TOOL_CHOICE_VALUES = tuple(choice.value for choice in ToolChoice) TOOL_CHOICE_TYPE = Literal[TOOL_CHOICE_VALUES] # type: ignore + class AgentState(str, Enum): """Agent execution states""" @@ -47,7 +54,7 @@ class ToolCall(BaseModel): class Message(BaseModel): """Represents a chat message in the conversation""" - role: ROLE_TYPE = Field(...) # type: ignore + role: ROLE_TYPE = Field(...) # type: ignore content: Optional[str] = Field(default=None) tool_calls: Optional[List[ToolCall]] = Field(default=None) name: Optional[str] = Field(default=None) @@ -104,7 +111,9 @@ class Message(BaseModel): @classmethod def tool_message(cls, content: str, name, tool_call_id: str) -> "Message": """Create a tool message""" - return cls(role=Role.TOOL, content=content, name=name, tool_call_id=tool_call_id) + return cls( + role=Role.TOOL, content=content, name=name, tool_call_id=tool_call_id + ) @classmethod def from_tool_calls( diff --git a/app/tool/python_execute.py b/app/tool/python_execute.py index e9c8140..08ceffa 100644 --- a/app/tool/python_execute.py +++ b/app/tool/python_execute.py @@ -1,6 +1,6 @@ +import multiprocessing import sys from io import StringIO -import multiprocessing from typing import Dict from app.tool.base import BaseTool @@ -53,17 +53,13 @@ class PythonExecute(BaseTool): """ with multiprocessing.Manager() as manager: - result = manager.dict({ - "observation": "", - "success": False - }) + result = manager.dict({"observation": "", "success": False}) if isinstance(__builtins__, dict): safe_globals = {"__builtins__": __builtins__} else: safe_globals = {"__builtins__": __builtins__.__dict__.copy()} proc = multiprocessing.Process( - target=self._run_code, - args=(code, result, safe_globals) + target=self._run_code, args=(code, result, safe_globals) ) proc.start() proc.join(timeout) diff --git a/app/tool/search/__init__.py b/app/tool/search/__init__.py index 509d16d..4f486ac 100644 --- a/app/tool/search/__init__.py +++ b/app/tool/search/__init__.py @@ -1,5 +1,5 @@ -from app.tool.search.base import WebSearchEngine from app.tool.search.baidu_search import BaiduSearchEngine +from app.tool.search.base import WebSearchEngine from app.tool.search.duckduckgo_search import DuckDuckGoSearchEngine from app.tool.search.google_search import GoogleSearchEngine @@ -9,4 +9,4 @@ __all__ = [ "BaiduSearchEngine", "DuckDuckGoSearchEngine", "GoogleSearchEngine", -] \ No newline at end of file +] diff --git a/app/tool/search/baidu_search.py b/app/tool/search/baidu_search.py index a398899..d415ce8 100644 --- a/app/tool/search/baidu_search.py +++ b/app/tool/search/baidu_search.py @@ -1,9 +1,9 @@ from baidusearch.baidusearch import search + from app.tool.search.base import WebSearchEngine class BaiduSearchEngine(WebSearchEngine): - - def perform_search(self, query, num_results = 10, *args, **kwargs): + def perform_search(self, query, num_results=10, *args, **kwargs): """Baidu search engine.""" return search(query, num_results=num_results) diff --git a/app/tool/search/base.py b/app/tool/search/base.py index 095c0b1..3132381 100644 --- a/app/tool/search/base.py +++ b/app/tool/search/base.py @@ -1,5 +1,7 @@ class WebSearchEngine(object): - def perform_search(self, query: str, num_results: int = 10, *args, **kwargs) -> list[dict]: + def perform_search( + self, query: str, num_results: int = 10, *args, **kwargs + ) -> list[dict]: """ Perform a web search and return a list of URLs. @@ -12,4 +14,4 @@ class WebSearchEngine(object): Returns: List: A list of dict matching the search query. """ - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/app/tool/search/duckduckgo_search.py b/app/tool/search/duckduckgo_search.py index 738ecf5..3dd5c52 100644 --- a/app/tool/search/duckduckgo_search.py +++ b/app/tool/search/duckduckgo_search.py @@ -1,9 +1,9 @@ from duckduckgo_search import DDGS + from app.tool.search.base import WebSearchEngine class DuckDuckGoSearchEngine(WebSearchEngine): - - async def perform_search(self, query, num_results = 10, *args, **kwargs): + async def perform_search(self, query, num_results=10, *args, **kwargs): """DuckDuckGo search engine.""" return DDGS.text(query, num_results=num_results) diff --git a/app/tool/search/google_search.py b/app/tool/search/google_search.py index 606f107..425106d 100644 --- a/app/tool/search/google_search.py +++ b/app/tool/search/google_search.py @@ -1,8 +1,9 @@ -from app.tool.search.base import WebSearchEngine from googlesearch import search +from app.tool.search.base import WebSearchEngine + + class GoogleSearchEngine(WebSearchEngine): - - def perform_search(self, query, num_results = 10, *args, **kwargs): + def perform_search(self, query, num_results=10, *args, **kwargs): """Google search engine.""" return search(query, num_results=num_results) diff --git a/app/tool/terminal.py b/app/tool/terminal.py index df5996e..86b401c 100644 --- a/app/tool/terminal.py +++ b/app/tool/terminal.py @@ -40,7 +40,7 @@ Note: You MUST append a `sleep 0.05` to the end of the command for commands that str: The output, and error of the command execution. """ # Split the command by & to handle multiple commands - commands = [cmd.strip() for cmd in command.split('&') if cmd.strip()] + commands = [cmd.strip() for cmd in command.split("&") if cmd.strip()] final_output = CLIResult(output="", error="") for cmd in commands: @@ -61,7 +61,7 @@ Note: You MUST append a `sleep 0.05` to the end of the command for commands that stdout, stderr = await self.process.communicate() result = CLIResult( output=stdout.decode().strip(), - error=stderr.decode().strip() + error=stderr.decode().strip(), ) except Exception as e: result = CLIResult(output="", error=str(e)) @@ -70,9 +70,13 @@ Note: You MUST append a `sleep 0.05` to the end of the command for commands that # Combine outputs if result.output: - final_output.output += (result.output + "\n") if final_output.output else result.output + final_output.output += ( + (result.output + "\n") if final_output.output else result.output + ) if result.error: - final_output.error += (result.error + "\n") if final_output.error else result.error + final_output.error += ( + (result.error + "\n") if final_output.error else result.error + ) # Remove trailing newlines final_output.output = final_output.output.rstrip() @@ -124,14 +128,10 @@ Note: You MUST append a `sleep 0.05` to the end of the command for commands that if os.path.isdir(new_path): self.current_path = new_path return CLIResult( - output=f"Changed directory to {self.current_path}", - error="" + output=f"Changed directory to {self.current_path}", error="" ) else: - return CLIResult( - output="", - error=f"No such directory: {new_path}" - ) + return CLIResult(output="", error=f"No such directory: {new_path}") except Exception as e: return CLIResult(output="", error=str(e)) @@ -152,7 +152,7 @@ Note: You MUST append a `sleep 0.05` to the end of the command for commands that parts = shlex.split(command) if any(cmd in dangerous_commands for cmd in parts): raise ValueError("Use of dangerous commands is restricted.") - except Exception as e: + except Exception: # If shlex.split fails, try basic string comparison if any(cmd in command for cmd in dangerous_commands): raise ValueError("Use of dangerous commands is restricted.") diff --git a/app/tool/web_search.py b/app/tool/web_search.py index c661f3b..db4ee85 100644 --- a/app/tool/web_search.py +++ b/app/tool/web_search.py @@ -1,9 +1,14 @@ import asyncio from typing import List -from app.tool.base import BaseTool from app.config import config -from app.tool.search import WebSearchEngine, BaiduSearchEngine, GoogleSearchEngine, DuckDuckGoSearchEngine +from app.tool.base import BaseTool +from app.tool.search import ( + BaiduSearchEngine, + DuckDuckGoSearchEngine, + GoogleSearchEngine, + WebSearchEngine, +) class WebSearch(BaseTool): @@ -48,7 +53,8 @@ The tool returns a list of URLs that match the search query. loop = asyncio.get_event_loop() search_engine = self.get_search_engine() links = await loop.run_in_executor( - None, lambda: list(search_engine.perform_search(query, num_results=num_results)) + None, + lambda: list(search_engine.perform_search(query, num_results=num_results)), ) return links diff --git a/config/config.example.toml b/config/config.example.toml index d6c193a..762f42c 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -1,10 +1,10 @@ # Global LLM configuration [llm] -model = "claude-3-5-sonnet" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." -max_tokens = 4096 -temperature = 0.0 +model = "claude-3-7-sonnet" # The LLM model to use +base_url = "https://api.openai.com/v1" # API endpoint URL +api_key = "sk-..." # Your API key +max_tokens = 8192 # Maximum number of tokens in the response +temperature = 0.0 # Controls randomness # [llm] #AZURE OPENAI: # api_type= 'azure' @@ -17,9 +17,11 @@ temperature = 0.0 # Optional configuration for specific LLM models [llm.vision] -model = "claude-3-5-sonnet" -base_url = "https://api.openai.com/v1" -api_key = "sk-..." +model = "claude-3-7-sonnet" # The vision model to use +base_url = "https://api.openai.com/v1" # API endpoint URL for vision model +api_key = "sk-..." # Your API key for vision model +max_tokens = 8192 # Maximum number of tokens in the response +temperature = 0.0 # Controls randomness for vision model # Optional configuration for specific browser configuration # [browser] @@ -46,4 +48,4 @@ api_key = "sk-..." # Optional configuration, Search settings. # [search] # Search engine for agent to use. Default is "Google", can be set to "Baidu" or "DuckDuckGo". -#engine = "Google" \ No newline at end of file +#engine = "Google" From 7db0b2fbf0bc8a00fd98dd8b5b6e4aa669dd56b2 Mon Sep 17 00:00:00 2001 From: liangxinbing <1580466765@qq.com> Date: Fri, 14 Mar 2025 12:27:05 +0800 Subject: [PATCH 5/7] update readme --- README.md | 2 ++ README_ja.md | 2 ++ README_ko.md | 2 ++ README_zh.md | 2 ++ 4 files changed, 8 insertions(+) diff --git a/README.md b/README.md index ae93a47..4e85c29 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ We welcome any friendly suggestions and helpful contributions! Just create issue Or contact @mannaandpoem via 📧email: mannaandpoem@gmail.com +**Note**: Before submitting a pull request, please use the pre-commit tool to check your changes. Run `pre-commit run --all-files` to execute the checks. + ## Community Group Join our networking group on Feishu and share your experience with other developers! diff --git a/README_ja.md b/README_ja.md index 2dd84d5..3805a69 100644 --- a/README_ja.md +++ b/README_ja.md @@ -128,6 +128,8 @@ python run_flow.py または @mannaandpoem に📧メールでご連絡ください:mannaandpoem@gmail.com +**注意**: プルリクエストを送信する前に、pre-commitツールを使用して変更を確認してください。`pre-commit run --all-files`を実行してチェックを実行します。 + ## コミュニティグループ Feishuのネットワーキンググループに参加して、他の開発者と経験を共有しましょう! diff --git a/README_ko.md b/README_ko.md index 379363e..940e9b9 100644 --- a/README_ko.md +++ b/README_ko.md @@ -128,6 +128,8 @@ python run_flow.py 또는 📧 메일로 연락주세요. @mannaandpoem : mannaandpoem@gmail.com +**참고**: pull request를 제출하기 전에 pre-commit 도구를 사용하여 변경 사항을 확인하십시오. `pre-commit run --all-files`를 실행하여 검사를 실행합니다. + ## 커뮤니티 그룹 Feishu 네트워킹 그룹에 참여하여 다른 개발자들과 경험을 공유하세요! diff --git a/README_zh.md b/README_zh.md index ea7f904..7f18d1c 100644 --- a/README_zh.md +++ b/README_zh.md @@ -131,6 +131,8 @@ python run_flow.py 或通过 📧 邮件联系 @mannaandpoem:mannaandpoem@gmail.com +**注意**: 在提交 pull request 之前,请使用 pre-commit 工具检查您的更改。运行 `pre-commit run --all-files` 来执行检查。 + ## 交流群 加入我们的飞书交流群,与其他开发者分享经验! From 7a5de556150a806e52a142cd5c555eb244457c92 Mon Sep 17 00:00:00 2001 From: the0807 Date: Fri, 14 Mar 2025 14:02:32 +0900 Subject: [PATCH 6/7] Add config support for Ollama --- config/config.example.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/config/config.example.toml b/config/config.example.toml index 762f42c..e9a9620 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -15,6 +15,14 @@ temperature = 0.0 # Controls randomness # temperature = 0.0 # api_version="AZURE API VERSION" #"2024-08-01-preview" +# [llm] #OLLAMA: +# api_type = 'ollama' +# model = "llama3.2" +# base_url = "http://localhost:11434/v1" +# api_key = "ollama" +# max_tokens = 4096 +# temperature = 0.0 + # Optional configuration for specific LLM models [llm.vision] model = "claude-3-7-sonnet" # The vision model to use @@ -23,6 +31,14 @@ api_key = "sk-..." # Your API key for vision model max_tokens = 8192 # Maximum number of tokens in the response temperature = 0.0 # Controls randomness for vision model +# [llm.vision] #OLLAMA VISION: +# api_type = 'ollama' +# model = "llama3.2-vision" +# base_url = "http://localhost:11434/v1" +# api_key = "ollama" +# max_tokens = 4096 +# temperature = 0.0 + # Optional configuration for specific browser configuration # [browser] # Whether to run browser in headless mode (default: false) From c0c03c0befe0db43920b9ec396ae5d5046de304a Mon Sep 17 00:00:00 2001 From: xiangjinyu <1376193973@qq.com> Date: Fri, 14 Mar 2025 13:25:43 +0800 Subject: [PATCH 7/7] fix _handle_special_tool bug --- app/agent/manus.py | 7 +++++-- app/tool/browser_use_tool.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/agent/manus.py b/app/agent/manus.py index 6c2c2e5..4638c37 100644 --- a/app/agent/manus.py +++ b/app/agent/manus.py @@ -39,5 +39,8 @@ class Manus(ToolCallAgent): ) async def _handle_special_tool(self, name: str, result: Any, **kwargs): - await self.available_tools.get_tool(BrowserUseTool().name).cleanup() - await super()._handle_special_tool(name, result, **kwargs) + if not self._is_special_tool(name): + return + else: + await self.available_tools.get_tool(BrowserUseTool().name).cleanup() + await super()._handle_special_tool(name, result, **kwargs) diff --git a/app/tool/browser_use_tool.py b/app/tool/browser_use_tool.py index 57ad03c..ad0cfa1 100644 --- a/app/tool/browser_use_tool.py +++ b/app/tool/browser_use_tool.py @@ -106,7 +106,7 @@ class BrowserUseTool(BaseTool): async def _ensure_browser_initialized(self) -> BrowserContext: """Ensure browser and context are initialized.""" if self.browser is None: - browser_config_kwargs = {"headless": False} + browser_config_kwargs = {"headless": False, "disable_security": True} if config.browser_config: from browser_use.browser.browser import ProxySettings