Merge branch 'main' into refactor/web-search-tool
This commit is contained in:
commit
6ea5a4d1ef
@ -127,6 +127,8 @@ We welcome any friendly suggestions and helpful contributions! Just create issue
|
|||||||
|
|
||||||
Or contact @mannaandpoem via 📧email: mannaandpoem@gmail.com
|
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
|
## Community Group
|
||||||
Join our networking group on Feishu and share your experience with other developers!
|
Join our networking group on Feishu and share your experience with other developers!
|
||||||
|
|
||||||
@ -143,7 +145,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)
|
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!
|
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!
|
OpenManus is built by contributors from MetaGPT. Huge thanks to this agent community!
|
||||||
|
|
||||||
|
@ -128,6 +128,8 @@ python run_flow.py
|
|||||||
|
|
||||||
または @mannaandpoem に📧メールでご連絡ください:mannaandpoem@gmail.com
|
または @mannaandpoem に📧メールでご連絡ください:mannaandpoem@gmail.com
|
||||||
|
|
||||||
|
**注意**: プルリクエストを送信する前に、pre-commitツールを使用して変更を確認してください。`pre-commit run --all-files`を実行してチェックを実行します。
|
||||||
|
|
||||||
## コミュニティグループ
|
## コミュニティグループ
|
||||||
Feishuのネットワーキンググループに参加して、他の開発者と経験を共有しましょう!
|
Feishuのネットワーキンググループに参加して、他の開発者と経験を共有しましょう!
|
||||||
|
|
||||||
@ -144,7 +146,7 @@ Feishuのネットワーキンググループに参加して、他の開発者
|
|||||||
このプロジェクトの基本的なサポートを提供してくれた[anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
|
このプロジェクトの基本的なサポートを提供してくれた[anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
|
||||||
と[browser-use](https://github.com/browser-use/browser-use)に感謝します!
|
と[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のコントリビューターによって構築されました。このエージェントコミュニティに大きな感謝を!
|
OpenManusはMetaGPTのコントリビューターによって構築されました。このエージェントコミュニティに大きな感謝を!
|
||||||
|
|
||||||
|
@ -128,6 +128,8 @@ python run_flow.py
|
|||||||
|
|
||||||
또는 📧 메일로 연락주세요. @mannaandpoem : mannaandpoem@gmail.com
|
또는 📧 메일로 연락주세요. @mannaandpoem : mannaandpoem@gmail.com
|
||||||
|
|
||||||
|
**참고**: pull request를 제출하기 전에 pre-commit 도구를 사용하여 변경 사항을 확인하십시오. `pre-commit run --all-files`를 실행하여 검사를 실행합니다.
|
||||||
|
|
||||||
## 커뮤니티 그룹
|
## 커뮤니티 그룹
|
||||||
Feishu 네트워킹 그룹에 참여하여 다른 개발자들과 경험을 공유하세요!
|
Feishu 네트워킹 그룹에 참여하여 다른 개발자들과 경험을 공유하세요!
|
||||||
|
|
||||||
@ -144,7 +146,7 @@ Feishu 네트워킹 그룹에 참여하여 다른 개발자들과 경험을 공
|
|||||||
이 프로젝트에 기본적인 지원을 제공해 주신 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)와
|
이 프로젝트에 기본적인 지원을 제공해 주신 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)와
|
||||||
[browser-use](https://github.com/browser-use/browser-use)에게 감사드립니다!
|
[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 기여자들에 의해 개발되었습니다. 이 에이전트 커뮤니티에 깊은 감사를 전합니다!
|
OpenManus는 MetaGPT 기여자들에 의해 개발되었습니다. 이 에이전트 커뮤니티에 깊은 감사를 전합니다!
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ python main.py
|
|||||||
|
|
||||||
然后通过终端输入你的创意!
|
然后通过终端输入你的创意!
|
||||||
|
|
||||||
如需体验开发中版本,可运行:
|
如需体验不稳定的开发版本,可运行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python run_flow.py
|
python run_flow.py
|
||||||
@ -131,6 +131,8 @@ python run_flow.py
|
|||||||
|
|
||||||
或通过 📧 邮件联系 @mannaandpoem:mannaandpoem@gmail.com
|
或通过 📧 邮件联系 @mannaandpoem:mannaandpoem@gmail.com
|
||||||
|
|
||||||
|
**注意**: 在提交 pull request 之前,请使用 pre-commit 工具检查您的更改。运行 `pre-commit run --all-files` 来执行检查。
|
||||||
|
|
||||||
## 交流群
|
## 交流群
|
||||||
|
|
||||||
加入我们的飞书交流群,与其他开发者分享经验!
|
加入我们的飞书交流群,与其他开发者分享经验!
|
||||||
@ -148,7 +150,7 @@ python run_flow.py
|
|||||||
特别感谢 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
|
特别感谢 [anthropic-computer-use](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)
|
||||||
和 [browser-use](https://github.com/browser-use/browser-use) 为本项目提供的基础支持!
|
和 [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 社区的贡献者共同构建,感谢这个充满活力的智能体开发者社区!
|
OpenManus 由 MetaGPT 社区的贡献者共同构建,感谢这个充满活力的智能体开发者社区!
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from pydantic import BaseModel, Field, model_validator
|
|||||||
|
|
||||||
from app.llm import LLM
|
from app.llm import LLM
|
||||||
from app.logger import logger
|
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):
|
class BaseAgent(BaseModel, ABC):
|
||||||
@ -82,7 +82,7 @@ class BaseAgent(BaseModel, ABC):
|
|||||||
|
|
||||||
def update_memory(
|
def update_memory(
|
||||||
self,
|
self,
|
||||||
role: ROLE_TYPE, # type: ignore
|
role: ROLE_TYPE, # type: ignore
|
||||||
content: str,
|
content: str,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -7,9 +7,8 @@ from app.prompt.manus import NEXT_STEP_PROMPT, SYSTEM_PROMPT
|
|||||||
from app.tool import Terminate, ToolCollection
|
from app.tool import Terminate, ToolCollection
|
||||||
from app.tool.browser_use_tool import BrowserUseTool
|
from app.tool.browser_use_tool import BrowserUseTool
|
||||||
from app.tool.file_saver import FileSaver
|
from app.tool.file_saver import FileSaver
|
||||||
from app.tool.web_search import WebSearch
|
|
||||||
from app.tool.python_execute import PythonExecute
|
from app.tool.python_execute import PythonExecute
|
||||||
from app.config import config
|
from app.tool.web_search import WebSearch
|
||||||
|
|
||||||
|
|
||||||
class Manus(ToolCallAgent):
|
class Manus(ToolCallAgent):
|
||||||
@ -40,5 +39,8 @@ class Manus(ToolCallAgent):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _handle_special_tool(self, name: str, result: Any, **kwargs):
|
async def _handle_special_tool(self, name: str, result: Any, **kwargs):
|
||||||
await self.available_tools.get_tool(BrowserUseTool().name).cleanup()
|
if not self._is_special_tool(name):
|
||||||
await super()._handle_special_tool(name, result, **kwargs)
|
return
|
||||||
|
else:
|
||||||
|
await self.available_tools.get_tool(BrowserUseTool().name).cleanup()
|
||||||
|
await super()._handle_special_tool(name, result, **kwargs)
|
||||||
|
@ -6,7 +6,7 @@ from pydantic import Field, model_validator
|
|||||||
from app.agent.toolcall import ToolCallAgent
|
from app.agent.toolcall import ToolCallAgent
|
||||||
from app.logger import logger
|
from app.logger import logger
|
||||||
from app.prompt.planning import NEXT_STEP_PROMPT, PLANNING_SYSTEM_PROMPT
|
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
|
from app.tool import PlanningTool, Terminate, ToolCollection
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class PlanningAgent(ToolCallAgent):
|
|||||||
available_tools: ToolCollection = Field(
|
available_tools: ToolCollection = Field(
|
||||||
default_factory=lambda: ToolCollection(PlanningTool(), Terminate())
|
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])
|
special_tool_names: List[str] = Field(default_factory=lambda: [Terminate().name])
|
||||||
|
|
||||||
tool_calls: List[ToolCall] = Field(default_factory=list)
|
tool_calls: List[ToolCall] = Field(default_factory=list)
|
||||||
@ -212,7 +212,7 @@ class PlanningAgent(ToolCallAgent):
|
|||||||
messages=messages,
|
messages=messages,
|
||||||
system_msgs=[Message.system_message(self.system_prompt)],
|
system_msgs=[Message.system_message(self.system_prompt)],
|
||||||
tools=self.available_tools.to_params(),
|
tools=self.available_tools.to_params(),
|
||||||
tool_choice=ToolChoice.REQUIRED,
|
tool_choice=ToolChoice.AUTO,
|
||||||
)
|
)
|
||||||
assistant_msg = Message.from_tool_calls(
|
assistant_msg = Message.from_tool_calls(
|
||||||
content=response.content, tool_calls=response.tool_calls
|
content=response.content, tool_calls=response.tool_calls
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
|
from typing import Any, List, Optional, Union
|
||||||
from typing import Any, List, Literal, Optional, Union
|
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from app.agent.react import ReActAgent
|
from app.agent.react import ReActAgent
|
||||||
from app.logger import logger
|
from app.logger import logger
|
||||||
from app.prompt.toolcall import NEXT_STEP_PROMPT, SYSTEM_PROMPT
|
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
|
from app.tool import CreateChatCompletion, Terminate, ToolCollection
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ class ToolCallAgent(ReActAgent):
|
|||||||
available_tools: ToolCollection = ToolCollection(
|
available_tools: ToolCollection = ToolCollection(
|
||||||
CreateChatCompletion(), Terminate()
|
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])
|
special_tool_names: List[str] = Field(default_factory=lambda: [Terminate().name])
|
||||||
|
|
||||||
tool_calls: List[ToolCall] = Field(default_factory=list)
|
tool_calls: List[ToolCall] = Field(default_factory=list)
|
||||||
|
@ -30,8 +30,10 @@ class ProxySettings(BaseModel):
|
|||||||
username: Optional[str] = Field(None, description="Proxy username")
|
username: Optional[str] = Field(None, description="Proxy username")
|
||||||
password: Optional[str] = Field(None, description="Proxy password")
|
password: Optional[str] = Field(None, description="Proxy password")
|
||||||
|
|
||||||
|
|
||||||
class SearchSettings(BaseModel):
|
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):
|
class BrowserSettings(BaseModel):
|
||||||
headless: bool = Field(False, description="Whether to run browser in headless mode")
|
headless: bool = Field(False, description="Whether to run browser in headless mode")
|
||||||
|
@ -124,7 +124,7 @@ class PlanningFlow(BaseFlow):
|
|||||||
messages=[user_message],
|
messages=[user_message],
|
||||||
system_msgs=[system_message],
|
system_msgs=[system_message],
|
||||||
tools=[self.planning_tool.to_param()],
|
tools=[self.planning_tool.to_param()],
|
||||||
tool_choice=ToolChoice.REQUIRED,
|
tool_choice=ToolChoice.AUTO,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process tool calls if present
|
# Process tool calls if present
|
||||||
|
68
app/llm.py
68
app/llm.py
@ -12,7 +12,16 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential
|
|||||||
|
|
||||||
from app.config import LLMSettings, config
|
from app.config import LLMSettings, config
|
||||||
from app.logger import logger # Assuming a logger is set up in your app
|
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:
|
class LLM:
|
||||||
@ -133,27 +142,30 @@ class LLM:
|
|||||||
else:
|
else:
|
||||||
messages = self.format_messages(messages)
|
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:
|
if not stream:
|
||||||
# Non-streaming request
|
# Non-streaming request
|
||||||
response = await self.client.chat.completions.create(
|
params["stream"] = False
|
||||||
model=self.model,
|
|
||||||
messages=messages,
|
response = await self.client.chat.completions.create(**params)
|
||||||
max_tokens=self.max_tokens,
|
|
||||||
temperature=temperature or self.temperature,
|
|
||||||
stream=False,
|
|
||||||
)
|
|
||||||
if not response.choices or not response.choices[0].message.content:
|
if not response.choices or not response.choices[0].message.content:
|
||||||
raise ValueError("Empty or invalid response from LLM")
|
raise ValueError("Empty or invalid response from LLM")
|
||||||
return response.choices[0].message.content
|
return response.choices[0].message.content
|
||||||
|
|
||||||
# Streaming request
|
# Streaming request
|
||||||
response = await self.client.chat.completions.create(
|
params["stream"] = True
|
||||||
model=self.model,
|
response = await self.client.chat.completions.create(**params)
|
||||||
messages=messages,
|
|
||||||
max_tokens=self.max_tokens,
|
|
||||||
temperature=temperature or self.temperature,
|
|
||||||
stream=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
collected_messages = []
|
collected_messages = []
|
||||||
async for chunk in response:
|
async for chunk in response:
|
||||||
@ -187,7 +199,7 @@ class LLM:
|
|||||||
system_msgs: Optional[List[Union[dict, Message]]] = None,
|
system_msgs: Optional[List[Union[dict, Message]]] = None,
|
||||||
timeout: int = 300,
|
timeout: int = 300,
|
||||||
tools: Optional[List[dict]] = None,
|
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,
|
temperature: Optional[float] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
@ -230,16 +242,22 @@ class LLM:
|
|||||||
raise ValueError("Each tool must be a dict with 'type' field")
|
raise ValueError("Each tool must be a dict with 'type' field")
|
||||||
|
|
||||||
# Set up the completion request
|
# Set up the completion request
|
||||||
response = await self.client.chat.completions.create(
|
params = {
|
||||||
model=self.model,
|
"model": self.model,
|
||||||
messages=messages,
|
"messages": messages,
|
||||||
temperature=temperature or self.temperature,
|
"tools": tools,
|
||||||
max_tokens=self.max_tokens,
|
"tool_choice": tool_choice,
|
||||||
tools=tools,
|
"timeout": timeout,
|
||||||
tool_choice=tool_choice,
|
|
||||||
timeout=timeout,
|
|
||||||
**kwargs,
|
**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
|
# Check if response is valid
|
||||||
if not response.choices or not response.choices[0].message:
|
if not response.choices or not response.choices[0].message:
|
||||||
|
@ -3,25 +3,32 @@ from typing import Any, List, Literal, Optional, Union
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class Role(str, Enum):
|
class Role(str, Enum):
|
||||||
"""Message role options"""
|
"""Message role options"""
|
||||||
|
|
||||||
SYSTEM = "system"
|
SYSTEM = "system"
|
||||||
USER = "user"
|
USER = "user"
|
||||||
ASSISTANT = "assistant"
|
ASSISTANT = "assistant"
|
||||||
TOOL = "tool"
|
TOOL = "tool"
|
||||||
|
|
||||||
|
|
||||||
ROLE_VALUES = tuple(role.value for role in Role)
|
ROLE_VALUES = tuple(role.value for role in Role)
|
||||||
ROLE_TYPE = Literal[ROLE_VALUES] # type: ignore
|
ROLE_TYPE = Literal[ROLE_VALUES] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class ToolChoice(str, Enum):
|
class ToolChoice(str, Enum):
|
||||||
"""Tool choice options"""
|
"""Tool choice options"""
|
||||||
|
|
||||||
NONE = "none"
|
NONE = "none"
|
||||||
AUTO = "auto"
|
AUTO = "auto"
|
||||||
REQUIRED = "required"
|
REQUIRED = "required"
|
||||||
|
|
||||||
|
|
||||||
TOOL_CHOICE_VALUES = tuple(choice.value for choice in ToolChoice)
|
TOOL_CHOICE_VALUES = tuple(choice.value for choice in ToolChoice)
|
||||||
TOOL_CHOICE_TYPE = Literal[TOOL_CHOICE_VALUES] # type: ignore
|
TOOL_CHOICE_TYPE = Literal[TOOL_CHOICE_VALUES] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class AgentState(str, Enum):
|
class AgentState(str, Enum):
|
||||||
"""Agent execution states"""
|
"""Agent execution states"""
|
||||||
|
|
||||||
@ -47,7 +54,7 @@ class ToolCall(BaseModel):
|
|||||||
class Message(BaseModel):
|
class Message(BaseModel):
|
||||||
"""Represents a chat message in the conversation"""
|
"""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)
|
content: Optional[str] = Field(default=None)
|
||||||
tool_calls: Optional[List[ToolCall]] = Field(default=None)
|
tool_calls: Optional[List[ToolCall]] = Field(default=None)
|
||||||
name: Optional[str] = Field(default=None)
|
name: Optional[str] = Field(default=None)
|
||||||
@ -104,7 +111,9 @@ class Message(BaseModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tool_message(cls, content: str, name, tool_call_id: str) -> "Message":
|
def tool_message(cls, content: str, name, tool_call_id: str) -> "Message":
|
||||||
"""Create a tool 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
|
@classmethod
|
||||||
def from_tool_calls(
|
def from_tool_calls(
|
||||||
|
@ -106,7 +106,7 @@ class BrowserUseTool(BaseTool):
|
|||||||
async def _ensure_browser_initialized(self) -> BrowserContext:
|
async def _ensure_browser_initialized(self) -> BrowserContext:
|
||||||
"""Ensure browser and context are initialized."""
|
"""Ensure browser and context are initialized."""
|
||||||
if self.browser is None:
|
if self.browser is None:
|
||||||
browser_config_kwargs = {"headless": False}
|
browser_config_kwargs = {"headless": False, "disable_security": True}
|
||||||
|
|
||||||
if config.browser_config:
|
if config.browser_config:
|
||||||
from browser_use.browser.browser import ProxySettings
|
from browser_use.browser.browser import ProxySettings
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import threading
|
import multiprocessing
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from app.tool.base import BaseTool
|
from app.tool.base import BaseTool
|
||||||
@ -20,6 +22,20 @@ class PythonExecute(BaseTool):
|
|||||||
"required": ["code"],
|
"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(
|
async def execute(
|
||||||
self,
|
self,
|
||||||
code: str,
|
code: str,
|
||||||
@ -35,36 +51,25 @@ class PythonExecute(BaseTool):
|
|||||||
Returns:
|
Returns:
|
||||||
Dict: Contains 'output' with execution output or error message and 'success' status.
|
Dict: Contains 'output' with execution output or error message and 'success' status.
|
||||||
"""
|
"""
|
||||||
result = {"observation": ""}
|
|
||||||
|
|
||||||
def run_code():
|
with multiprocessing.Manager() as manager:
|
||||||
try:
|
result = manager.dict({"observation": "", "success": False})
|
||||||
safe_globals = {"__builtins__": dict(__builtins__)}
|
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
|
# timeout process
|
||||||
from io import StringIO
|
if proc.is_alive():
|
||||||
|
proc.terminate()
|
||||||
output_buffer = StringIO()
|
proc.join(1)
|
||||||
sys.stdout = output_buffer
|
return {
|
||||||
|
"observation": f"Execution timeout after {timeout} seconds",
|
||||||
exec(code, safe_globals, {})
|
"success": False,
|
||||||
|
}
|
||||||
sys.stdout = sys.__stdout__
|
return dict(result)
|
||||||
|
|
||||||
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
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from app.tool.search.base import WebSearchEngine
|
|
||||||
from app.tool.search.baidu_search import BaiduSearchEngine
|
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.duckduckgo_search import DuckDuckGoSearchEngine
|
||||||
from app.tool.search.google_search import GoogleSearchEngine
|
from app.tool.search.google_search import GoogleSearchEngine
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from baidusearch.baidusearch import search
|
from baidusearch.baidusearch import search
|
||||||
|
|
||||||
from app.tool.search.base import WebSearchEngine
|
from app.tool.search.base import WebSearchEngine
|
||||||
|
|
||||||
|
|
||||||
class BaiduSearchEngine(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."""
|
"""Baidu search engine."""
|
||||||
return search(query, num_results=num_results)
|
return search(query, num_results=num_results)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
class WebSearchEngine(object):
|
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.
|
Perform a web search and return a list of URLs.
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from duckduckgo_search import DDGS
|
from duckduckgo_search import DDGS
|
||||||
|
|
||||||
from app.tool.search.base import WebSearchEngine
|
from app.tool.search.base import WebSearchEngine
|
||||||
|
|
||||||
|
|
||||||
class DuckDuckGoSearchEngine(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."""
|
"""DuckDuckGo search engine."""
|
||||||
return DDGS.text(query, num_results=num_results)
|
return DDGS.text(query, num_results=num_results)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from app.tool.search.base import WebSearchEngine
|
|
||||||
from googlesearch import search
|
from googlesearch import search
|
||||||
|
|
||||||
class GoogleSearchEngine(WebSearchEngine):
|
from app.tool.search.base import WebSearchEngine
|
||||||
|
|
||||||
def perform_search(self, query, num_results = 10, *args, **kwargs):
|
|
||||||
|
class GoogleSearchEngine(WebSearchEngine):
|
||||||
|
def perform_search(self, query, num_results=10, *args, **kwargs):
|
||||||
"""Google search engine."""
|
"""Google search engine."""
|
||||||
return search(query, num_results=num_results)
|
return search(query, num_results=num_results)
|
||||||
|
@ -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.
|
str: The output, and error of the command execution.
|
||||||
"""
|
"""
|
||||||
# Split the command by & to handle multiple commands
|
# 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="")
|
final_output = CLIResult(output="", error="")
|
||||||
|
|
||||||
for cmd in commands:
|
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()
|
stdout, stderr = await self.process.communicate()
|
||||||
result = CLIResult(
|
result = CLIResult(
|
||||||
output=stdout.decode().strip(),
|
output=stdout.decode().strip(),
|
||||||
error=stderr.decode().strip()
|
error=stderr.decode().strip(),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = CLIResult(output="", error=str(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
|
# Combine outputs
|
||||||
if result.output:
|
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:
|
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
|
# Remove trailing newlines
|
||||||
final_output.output = final_output.output.rstrip()
|
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):
|
if os.path.isdir(new_path):
|
||||||
self.current_path = new_path
|
self.current_path = new_path
|
||||||
return CLIResult(
|
return CLIResult(
|
||||||
output=f"Changed directory to {self.current_path}",
|
output=f"Changed directory to {self.current_path}", error=""
|
||||||
error=""
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return CLIResult(
|
return CLIResult(output="", error=f"No such directory: {new_path}")
|
||||||
output="",
|
|
||||||
error=f"No such directory: {new_path}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return CLIResult(output="", error=str(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)
|
parts = shlex.split(command)
|
||||||
if any(cmd in dangerous_commands for cmd in parts):
|
if any(cmd in dangerous_commands for cmd in parts):
|
||||||
raise ValueError("Use of dangerous commands is restricted.")
|
raise ValueError("Use of dangerous commands is restricted.")
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# If shlex.split fails, try basic string comparison
|
# If shlex.split fails, try basic string comparison
|
||||||
if any(cmd in command for cmd in dangerous_commands):
|
if any(cmd in command for cmd in dangerous_commands):
|
||||||
raise ValueError("Use of dangerous commands is restricted.")
|
raise ValueError("Use of dangerous commands is restricted.")
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from app.tool.base import BaseTool
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
from app.tool.search import WebSearchEngine, BaiduSearchEngine, GoogleSearchEngine, DuckDuckGoSearchEngine
|
|
||||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||||
|
from app.tool.base import BaseTool
|
||||||
|
from app.tool.search import (
|
||||||
|
BaiduSearchEngine,
|
||||||
|
DuckDuckGoSearchEngine,
|
||||||
|
GoogleSearchEngine,
|
||||||
|
WebSearchEngine,
|
||||||
|
)
|
||||||
|
|
||||||
class WebSearch(BaseTool):
|
class WebSearch(BaseTool):
|
||||||
name: str = "web_search"
|
name: str = "web_search"
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# Global LLM configuration
|
# Global LLM configuration
|
||||||
[llm]
|
[llm]
|
||||||
model = "claude-3-5-sonnet"
|
model = "claude-3-7-sonnet" # The LLM model to use
|
||||||
base_url = "https://api.openai.com/v1"
|
base_url = "https://api.openai.com/v1" # API endpoint URL
|
||||||
api_key = "sk-..."
|
api_key = "sk-..." # Your API key
|
||||||
max_tokens = 4096
|
max_tokens = 8192 # Maximum number of tokens in the response
|
||||||
temperature = 0.0
|
temperature = 0.0 # Controls randomness
|
||||||
|
|
||||||
# [llm] #AZURE OPENAI:
|
# [llm] #AZURE OPENAI:
|
||||||
# api_type= 'azure'
|
# api_type= 'azure'
|
||||||
@ -15,11 +15,29 @@ temperature = 0.0
|
|||||||
# temperature = 0.0
|
# temperature = 0.0
|
||||||
# api_version="AZURE API VERSION" #"2024-08-01-preview"
|
# 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
|
# Optional configuration for specific LLM models
|
||||||
[llm.vision]
|
[llm.vision]
|
||||||
model = "claude-3-5-sonnet"
|
model = "claude-3-7-sonnet" # The vision model to use
|
||||||
base_url = "https://api.openai.com/v1"
|
base_url = "https://api.openai.com/v1" # API endpoint URL for vision model
|
||||||
api_key = "sk-..."
|
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
|
# Optional configuration for specific browser configuration
|
||||||
# [browser]
|
# [browser]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user