Merge branch 'main' into main
This commit is contained in:
commit
729e824e4e
@ -1,3 +1,7 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="assets/logo.jpg" width="200"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
English | [中文](README_zh.md) | [한국어](README_ko.md) | [日本語](README_ja.md)
|
English | [中文](README_zh.md) | [한국어](README_ko.md) | [日本語](README_ja.md)
|
||||||
|
|
||||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||||
@ -65,7 +69,7 @@ cd OpenManus
|
|||||||
3. Create a new virtual environment and activate it:
|
3. Create a new virtual environment and activate it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv venv
|
uv venv --python 3.12
|
||||||
source .venv/bin/activate # On Unix/macOS
|
source .venv/bin/activate # On Unix/macOS
|
||||||
# Or on Windows:
|
# Or on Windows:
|
||||||
# .venv\Scripts\activate
|
# .venv\Scripts\activate
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
[English](README.md) | [中文](README_zh.md) | [한국어](README_ko.md) | 日本語
|
<p align="center">
|
||||||
|
<img src="assets/logo.jpg" width="200"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[English](README.md) | [中文](README_zh.md) | [한국어](README_ko.md) | 日本語
|
||||||
|
|
||||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||||
 
|
 
|
||||||
@ -66,7 +69,7 @@ cd OpenManus
|
|||||||
3. 新しい仮想環境を作成してアクティベートします:
|
3. 新しい仮想環境を作成してアクティベートします:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv venv
|
uv venv --python 3.12
|
||||||
source .venv/bin/activate # Unix/macOSの場合
|
source .venv/bin/activate # Unix/macOSの場合
|
||||||
# Windowsの場合:
|
# Windowsの場合:
|
||||||
# .venv\Scripts\activate
|
# .venv\Scripts\activate
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
[English](README.md) | [中文](README_zh.md) | 한국어 | [日本語](README_ja.md)
|
<p align="center">
|
||||||
|
<img src="assets/logo.jpg" width="200"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[English](README.md) | [中文](README_zh.md) | 한국어 | [日本語](README_ja.md)
|
||||||
|
|
||||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||||
 
|
 
|
||||||
@ -66,7 +69,7 @@ cd OpenManus
|
|||||||
3. 새로운 가상 환경을 생성하고 활성화합니다:
|
3. 새로운 가상 환경을 생성하고 활성화합니다:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv venv
|
uv venv --python 3.12
|
||||||
source .venv/bin/activate # Unix/macOS의 경우
|
source .venv/bin/activate # Unix/macOS의 경우
|
||||||
# Windows의 경우:
|
# Windows의 경우:
|
||||||
# .venv\Scripts\activate
|
# .venv\Scripts\activate
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="assets/logo.jpg" width="200"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
[English](README.md) | 中文 | [한국어](README_ko.md) | [日本語](README_ja.md)
|
[English](README.md) | 中文 | [한국어](README_ko.md) | [日本語](README_ja.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||||
 
|
 
|
||||||
[](https://opensource.org/licenses/MIT)  
|
[](https://opensource.org/licenses/MIT)  
|
||||||
@ -69,7 +70,7 @@ cd OpenManus
|
|||||||
3. 创建并激活虚拟环境:
|
3. 创建并激活虚拟环境:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv venv
|
uv venv --python 3.12
|
||||||
source .venv/bin/activate # Unix/macOS 系统
|
source .venv/bin/activate # Unix/macOS 系统
|
||||||
# Windows 系统使用:
|
# Windows 系统使用:
|
||||||
# .venv\Scripts\activate
|
# .venv\Scripts\activate
|
||||||
|
15
app/llm.py
15
app/llm.py
@ -180,14 +180,15 @@ class LLM:
|
|||||||
formatted_messages = []
|
formatted_messages = []
|
||||||
|
|
||||||
for message in messages:
|
for message in messages:
|
||||||
|
if isinstance(message, Message):
|
||||||
|
message = message.to_dict()
|
||||||
if isinstance(message, 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:
|
if "role" not in message:
|
||||||
raise ValueError("Message dict must contain 'role' field")
|
raise ValueError("Message dict must contain 'role' field")
|
||||||
formatted_messages.append(message)
|
if "content" in message or "tool_calls" in message:
|
||||||
elif isinstance(message, Message):
|
formatted_messages.append(message)
|
||||||
# If message is a Message object, convert it to dict
|
# else: do not include the message
|
||||||
formatted_messages.append(message.to_dict())
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Unsupported message type: {type(message)}")
|
raise TypeError(f"Unsupported message type: {type(message)}")
|
||||||
|
|
||||||
@ -195,10 +196,6 @@ class LLM:
|
|||||||
for msg in formatted_messages:
|
for msg in formatted_messages:
|
||||||
if msg["role"] not in ROLE_VALUES:
|
if msg["role"] not in ROLE_VALUES:
|
||||||
raise ValueError(f"Invalid role: {msg['role']}")
|
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
|
return formatted_messages
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import asyncio
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||||
from app.tool.base import BaseTool
|
from app.tool.base import BaseTool
|
||||||
from app.tool.search import (
|
from app.tool.search import (
|
||||||
BaiduSearchEngine,
|
BaiduSearchEngine,
|
||||||
@ -10,13 +11,11 @@ from app.tool.search import (
|
|||||||
WebSearchEngine,
|
WebSearchEngine,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WebSearch(BaseTool):
|
class WebSearch(BaseTool):
|
||||||
name: str = "web_search"
|
name: str = "web_search"
|
||||||
description: str = """Perform a web search and return a list of relevant links.
|
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.
|
This function attempts to use the primary search engine API to get up-to-date results.
|
||||||
The tool returns a list of URLs that match the search query.
|
If an error occurs, it falls back to an alternative search engine."""
|
||||||
"""
|
|
||||||
parameters: dict = {
|
parameters: dict = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -49,21 +48,48 @@ The tool returns a list of URLs that match the search query.
|
|||||||
Returns:
|
Returns:
|
||||||
List[str]: A list of URLs matching the search query.
|
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()
|
loop = asyncio.get_event_loop()
|
||||||
search_engine = self.get_search_engine()
|
return await loop.run_in_executor(
|
||||||
links = await loop.run_in_executor(
|
None, lambda: list(engine.perform_search(query, num_results=num_results))
|
||||||
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)
|
|
||||||
|
BIN
assets/logo.jpg
Normal file
BIN
assets/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
Loading…
x
Reference in New Issue
Block a user