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)
[](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) | 日本語
[](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)
[](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)
-
-
[](https://github.com/mannaandpoem/OpenManus/stargazers)
[](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