diff --git a/README.md b/README.md index 4e85c29..d8e5bb0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +

+ +

+ English | [中文](README_zh.md) | [한국어](README_ko.md) | [日本語](README_ja.md) [![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers) @@ -65,7 +69,7 @@ cd OpenManus 3. Create a new virtual environment and activate it: ```bash -uv venv +uv venv --python 3.12 source .venv/bin/activate # On Unix/macOS # Or on Windows: # .venv\Scripts\activate diff --git a/README_ja.md b/README_ja.md index 3805a69..71e5b68 100644 --- a/README_ja.md +++ b/README_ja.md @@ -1,5 +1,8 @@ -[English](README.md) | [中文](README_zh.md) | [한국어](README_ko.md) | 日本語 +

+ +

+[English](README.md) | [中文](README_zh.md) | [한국어](README_ko.md) | 日本語 [![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers)   @@ -66,7 +69,7 @@ cd OpenManus 3. 新しい仮想環境を作成してアクティベートします: ```bash -uv venv +uv venv --python 3.12 source .venv/bin/activate # Unix/macOSの場合 # Windowsの場合: # .venv\Scripts\activate diff --git a/README_ko.md b/README_ko.md index 940e9b9..1a00afb 100644 --- a/README_ko.md +++ b/README_ko.md @@ -1,5 +1,8 @@ -[English](README.md) | [中文](README_zh.md) | 한국어 | [日本語](README_ja.md) +

+ +

+[English](README.md) | [中文](README_zh.md) | 한국어 | [日本語](README_ja.md) [![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers)   @@ -66,7 +69,7 @@ cd OpenManus 3. 새로운 가상 환경을 생성하고 활성화합니다: ```bash -uv venv +uv venv --python 3.12 source .venv/bin/activate # Unix/macOS의 경우 # Windows의 경우: # .venv\Scripts\activate diff --git a/README_zh.md b/README_zh.md index 7f18d1c..15e010b 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,8 +1,9 @@ +

+ +

[English](README.md) | 中文 | [한국어](README_ko.md) | [日本語](README_ja.md) - - [![GitHub stars](https://img.shields.io/github/stars/mannaandpoem/OpenManus?style=social)](https://github.com/mannaandpoem/OpenManus/stargazers)   [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)   @@ -69,7 +70,7 @@ cd OpenManus 3. 创建并激活虚拟环境: ```bash -uv venv +uv venv --python 3.12 source .venv/bin/activate # Unix/macOS 系统 # Windows 系统使用: # .venv\Scripts\activate diff --git a/app/llm.py b/app/llm.py index 1cc640b..18a13af 100644 --- a/app/llm.py +++ b/app/llm.py @@ -180,14 +180,15 @@ class LLM: formatted_messages = [] for message in messages: + if isinstance(message, Message): + message = message.to_dict() if isinstance(message, dict): - # If message is already a dict, ensure it has required fields + # If message is a dict, ensure it has required fields if "role" not in message: raise ValueError("Message dict must contain 'role' field") - formatted_messages.append(message) - elif isinstance(message, Message): - # If message is a Message object, convert it to dict - formatted_messages.append(message.to_dict()) + if "content" in message or "tool_calls" in message: + formatted_messages.append(message) + # else: do not include the message else: raise TypeError(f"Unsupported message type: {type(message)}") @@ -195,10 +196,6 @@ class LLM: for msg in formatted_messages: if msg["role"] not in ROLE_VALUES: raise ValueError(f"Invalid role: {msg['role']}") - if "content" not in msg and "tool_calls" not in msg: - raise ValueError( - "Message must contain either 'content' or 'tool_calls'" - ) return formatted_messages diff --git a/app/tool/web_search.py b/app/tool/web_search.py index db4ee85..cd23f5d 100644 --- a/app/tool/web_search.py +++ b/app/tool/web_search.py @@ -2,6 +2,7 @@ import asyncio from typing import List from app.config import config +from tenacity import retry, stop_after_attempt, wait_exponential from app.tool.base import BaseTool from app.tool.search import ( BaiduSearchEngine, @@ -10,13 +11,11 @@ from app.tool.search import ( WebSearchEngine, ) - class WebSearch(BaseTool): name: str = "web_search" - description: str = """Perform a web search and return a list of relevant links. -Use this tool when you need to find information on the web, get up-to-date data, or research specific topics. -The tool returns a list of URLs that match the search query. -""" + description: str = """Perform a web search and return a list of relevant links. + This function attempts to use the primary search engine API to get up-to-date results. + If an error occurs, it falls back to an alternative search engine.""" parameters: dict = { "type": "object", "properties": { @@ -49,21 +48,48 @@ The tool returns a list of URLs that match the search query. Returns: List[str]: A list of URLs matching the search query. """ - # Run the search in a thread pool to prevent blocking + engine_order = self._get_engine_order() + for engine_name in engine_order: + engine = self._search_engine[engine_name] + try: + links = await self._perform_search_with_engine(engine, query, num_results) + if links: + return links + except Exception as e: + print(f"Search engine '{engine_name}' failed with error: {e}") + return [] + + def _get_engine_order(self) -> List[str]: + """ + Determines the order in which to try search engines. + Preferred engine is first (based on configuration), followed by the remaining engines. + + Returns: + List[str]: Ordered list of search engine names. + """ + preferred = "google" + if config.search_config and config.search_config.engine: + preferred = config.search_config.engine.lower() + + engine_order = [] + if preferred in self._search_engine: + engine_order.append(preferred) + for key in self._search_engine: + if key not in engine_order: + engine_order.append(key) + return engine_order + + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=10), + ) + async def _perform_search_with_engine( + self, + engine: WebSearchEngine, + query: str, + num_results: int, + ) -> List[str]: 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)), - ) - - return links - - def get_search_engine(self) -> WebSearchEngine: - """Determines the search engine to use based on the configuration.""" - default_engine = self._search_engine.get("google") - if config.search_config is None: - return default_engine - else: - engine = config.search_config.engine.lower() - return self._search_engine.get(engine, default_engine) + return await loop.run_in_executor( + None, lambda: list(engine.perform_search(query, num_results=num_results)) + ) \ No newline at end of file diff --git a/assets/logo.jpg b/assets/logo.jpg new file mode 100644 index 0000000..634b8f6 Binary files /dev/null and b/assets/logo.jpg differ