Merge branch 'main' of https://github.com/mannaandpoem/OpenManus into mcp
This commit is contained in:
commit
792dc664a7
@ -1,4 +1,4 @@
|
||||
English | [中文](README_zh.md)
|
||||
English | [中文](README_zh.md) | [한국어](README_ko.md) | [日本語](README_ja.md)
|
||||
|
||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||
 
|
||||
@ -143,6 +143,8 @@ 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).
|
||||
|
||||
OpenManus is built by contributors from MetaGPT. Huge thanks to this agent community!
|
||||
|
||||
## Cite
|
||||
|
160
README_ja.md
Normal file
160
README_ja.md
Normal file
@ -0,0 +1,160 @@
|
||||
[English](README.md) | [中文](README_zh.md) | [한국어](README_ko.md) | 日本語
|
||||
|
||||
|
||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||
 
|
||||
[](https://opensource.org/licenses/MIT)  
|
||||
[](https://discord.gg/DYn29wFk9z)
|
||||
|
||||
# 👋 OpenManus
|
||||
|
||||
Manusは素晴らしいですが、OpenManusは*招待コード*なしでどんなアイデアも実現できます!🛫
|
||||
|
||||
私たちのチームメンバー [@Xinbin Liang](https://github.com/mannaandpoem) と [@Jinyu Xiang](https://github.com/XiangJinyu)(主要開発者)、そして [@Zhaoyang Yu](https://github.com/MoshiQAQ)、[@Jiayi Zhang](https://github.com/didiforgithub)、[@Sirui Hong](https://github.com/stellaHSR) は [@MetaGPT](https://github.com/geekan/MetaGPT) から来ました。プロトタイプは3時間以内に立ち上げられ、継続的に開発を進めています!
|
||||
|
||||
これはシンプルな実装ですので、どんな提案、貢献、フィードバックも歓迎します!
|
||||
|
||||
OpenManusで自分だけのエージェントを楽しみましょう!
|
||||
|
||||
また、UIUCとOpenManusの研究者が共同開発した[OpenManus-RL](https://github.com/OpenManus/OpenManus-RL)をご紹介できることを嬉しく思います。これは強化学習(RL)ベース(GRPOなど)のLLMエージェントチューニング手法に特化したオープンソースプロジェクトです。
|
||||
|
||||
## プロジェクトデモ
|
||||
|
||||
<video src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" data-canonical-src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; min-height: 200px"></video>
|
||||
|
||||
## インストール方法
|
||||
|
||||
インストール方法は2つ提供しています。方法2(uvを使用)は、より高速なインストールと優れた依存関係管理のため推奨されています。
|
||||
|
||||
### 方法1:condaを使用
|
||||
|
||||
1. 新しいconda環境を作成します:
|
||||
|
||||
```bash
|
||||
conda create -n open_manus python=3.12
|
||||
conda activate open_manus
|
||||
```
|
||||
|
||||
2. リポジトリをクローンします:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mannaandpoem/OpenManus.git
|
||||
cd OpenManus
|
||||
```
|
||||
|
||||
3. 依存関係をインストールします:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 方法2:uvを使用(推奨)
|
||||
|
||||
1. uv(高速なPythonパッケージインストーラーと管理機能)をインストールします:
|
||||
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
2. リポジトリをクローンします:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mannaandpoem/OpenManus.git
|
||||
cd OpenManus
|
||||
```
|
||||
|
||||
3. 新しい仮想環境を作成してアクティベートします:
|
||||
|
||||
```bash
|
||||
uv venv
|
||||
source .venv/bin/activate # Unix/macOSの場合
|
||||
# Windowsの場合:
|
||||
# .venv\Scripts\activate
|
||||
```
|
||||
|
||||
4. 依存関係をインストールします:
|
||||
|
||||
```bash
|
||||
uv pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 設定
|
||||
|
||||
OpenManusを使用するには、LLM APIの設定が必要です。以下の手順に従って設定してください:
|
||||
|
||||
1. `config`ディレクトリに`config.toml`ファイルを作成します(サンプルからコピーできます):
|
||||
|
||||
```bash
|
||||
cp config/config.example.toml config/config.toml
|
||||
```
|
||||
|
||||
2. `config/config.toml`を編集してAPIキーを追加し、設定をカスタマイズします:
|
||||
|
||||
```toml
|
||||
# グローバルLLM設定
|
||||
[llm]
|
||||
model = "gpt-4o"
|
||||
base_url = "https://api.openai.com/v1"
|
||||
api_key = "sk-..." # 実際のAPIキーに置き換えてください
|
||||
max_tokens = 4096
|
||||
temperature = 0.0
|
||||
|
||||
# 特定のLLMモデル用のオプション設定
|
||||
[llm.vision]
|
||||
model = "gpt-4o"
|
||||
base_url = "https://api.openai.com/v1"
|
||||
api_key = "sk-..." # 実際のAPIキーに置き換えてください
|
||||
```
|
||||
|
||||
## クイックスタート
|
||||
|
||||
OpenManusを実行する一行コマンド:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
その後、ターミナルからプロンプトを入力してください!
|
||||
|
||||
開発中バージョンを試すには、以下を実行します:
|
||||
|
||||
```bash
|
||||
python run_flow.py
|
||||
```
|
||||
|
||||
## 貢献方法
|
||||
|
||||
我々は建設的な意見や有益な貢献を歓迎します!issueを作成するか、プルリクエストを提出してください。
|
||||
|
||||
または @mannaandpoem に📧メールでご連絡ください:mannaandpoem@gmail.com
|
||||
|
||||
## コミュニティグループ
|
||||
Feishuのネットワーキンググループに参加して、他の開発者と経験を共有しましょう!
|
||||
|
||||
<div align="center" style="display: flex; gap: 20px;">
|
||||
<img src="assets/community_group.jpg" alt="OpenManus 交流群" width="300" />
|
||||
</div>
|
||||
|
||||
## スター履歴
|
||||
|
||||
[](https://star-history.com/#mannaandpoem/OpenManus&Date)
|
||||
|
||||
## 謝辞
|
||||
|
||||
このプロジェクトの基本的なサポートを提供してくれた[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)にも感謝します。
|
||||
|
||||
OpenManusはMetaGPTのコントリビューターによって構築されました。このエージェントコミュニティに大きな感謝を!
|
||||
|
||||
## 引用
|
||||
```bibtex
|
||||
@misc{openmanus2025,
|
||||
author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong},
|
||||
title = {OpenManus: An open-source framework for building general AI agents},
|
||||
year = {2025},
|
||||
publisher = {GitHub},
|
||||
journal = {GitHub repository},
|
||||
howpublished = {\url{https://github.com/mannaandpoem/OpenManus}},
|
||||
}
|
161
README_ko.md
Normal file
161
README_ko.md
Normal file
@ -0,0 +1,161 @@
|
||||
[English](README.md) | [中文](README_zh.md) | 한국어 | [日本語](README_ja.md)
|
||||
|
||||
|
||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||
 
|
||||
[](https://opensource.org/licenses/MIT)  
|
||||
[](https://discord.gg/DYn29wFk9z)
|
||||
|
||||
# 👋 OpenManus
|
||||
|
||||
Manus는 놀라운 도구지만, OpenManus는 *초대 코드* 없이도 모든 아이디어를 실현할 수 있습니다! 🛫
|
||||
|
||||
우리 팀의 멤버인 [@Xinbin Liang](https://github.com/mannaandpoem)와 [@Jinyu Xiang](https://github.com/XiangJinyu) (핵심 작성자), 그리고 [@Zhaoyang Yu](https://github.com/MoshiQAQ), [@Jiayi Zhang](https://github.com/didiforgithub), [@Sirui Hong](https://github.com/stellaHSR)이 함께 했습니다. 우리는 [@MetaGPT](https://github.com/geekan/MetaGPT)로부터 왔습니다. 프로토타입은 단 3시간 만에 출시되었으며, 계속해서 발전하고 있습니다!
|
||||
|
||||
이 프로젝트는 간단한 구현에서 시작되었으며, 여러분의 제안, 기여 및 피드백을 환영합니다!
|
||||
|
||||
OpenManus를 통해 여러분만의 에이전트를 즐겨보세요!
|
||||
|
||||
또한 [OpenManus-RL](https://github.com/OpenManus/OpenManus-RL)을 소개하게 되어 기쁩니다. OpenManus와 UIUC 연구자들이 공동 개발한 이 오픈소스 프로젝트는 LLM 에이전트에 대해 강화 학습(RL) 기반 (예: GRPO) 튜닝 방법을 제공합니다.
|
||||
|
||||
## 프로젝트 데모
|
||||
|
||||
<video src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" data-canonical-src="https://private-user-images.githubusercontent.com/61239030/420168772-6dcfd0d2-9142-45d9-b74e-d10aa75073c6.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDEzMTgwNTksIm5iZiI6MTc0MTMxNzc1OSwicGF0aCI6Ii82MTIzOTAzMC80MjAxNjg3NzItNmRjZmQwZDItOTE0Mi00NWQ5LWI3NGUtZDEwYWE3NTA3M2M2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzA3VDAzMjIzOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjFkNjlmYWNjMmEzOTliM2Y3M2VlYjgyNDRlZDJmOWE3NWZhZjE1MzhiZWY4YmQ3NjdkNTYwYTU5ZDA2MzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.UuHQCgWYkh0OQq9qsUWqGsUbhG3i9jcZDAMeHjLt5T4" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; min-height: 200px"></video>
|
||||
|
||||
## 설치 방법
|
||||
|
||||
두 가지 설치 방법을 제공합니다. **방법 2 (uv 사용)** 이 더 빠른 설치와 효율적인 종속성 관리를 위해 권장됩니다.
|
||||
|
||||
### 방법 1: conda 사용
|
||||
|
||||
1. 새로운 conda 환경을 생성합니다:
|
||||
|
||||
```bash
|
||||
conda create -n open_manus python=3.12
|
||||
conda activate open_manus
|
||||
```
|
||||
|
||||
2. 저장소를 클론합니다:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mannaandpoem/OpenManus.git
|
||||
cd OpenManus
|
||||
```
|
||||
|
||||
3. 종속성을 설치합니다:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 방법 2: uv 사용 (권장)
|
||||
|
||||
1. uv를 설치합니다. (빠른 Python 패키지 설치 및 종속성 관리 도구):
|
||||
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
2. 저장소를 클론합니다:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mannaandpoem/OpenManus.git
|
||||
cd OpenManus
|
||||
```
|
||||
|
||||
3. 새로운 가상 환경을 생성하고 활성화합니다:
|
||||
|
||||
```bash
|
||||
uv venv
|
||||
source .venv/bin/activate # Unix/macOS의 경우
|
||||
# Windows의 경우:
|
||||
# .venv\Scripts\activate
|
||||
```
|
||||
|
||||
4. 종속성을 설치합니다:
|
||||
|
||||
```bash
|
||||
uv pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 설정 방법
|
||||
|
||||
OpenManus를 사용하려면 사용하는 LLM API에 대한 설정이 필요합니다. 아래 단계를 따라 설정을 완료하세요:
|
||||
|
||||
1. `config` 디렉토리에 `config.toml` 파일을 생성하세요 (예제 파일을 복사하여 사용할 수 있습니다):
|
||||
|
||||
```bash
|
||||
cp config/config.example.toml config/config.toml
|
||||
```
|
||||
|
||||
2. `config/config.toml` 파일을 편집하여 API 키를 추가하고 설정을 커스터마이징하세요:
|
||||
|
||||
```toml
|
||||
# 전역 LLM 설정
|
||||
[llm]
|
||||
model = "gpt-4o"
|
||||
base_url = "https://api.openai.com/v1"
|
||||
api_key = "sk-..." # 실제 API 키로 변경하세요
|
||||
max_tokens = 4096
|
||||
temperature = 0.0
|
||||
|
||||
# 특정 LLM 모델에 대한 선택적 설정
|
||||
[llm.vision]
|
||||
model = "gpt-4o"
|
||||
base_url = "https://api.openai.com/v1"
|
||||
api_key = "sk-..." # 실제 API 키로 변경하세요
|
||||
```
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
OpenManus를 실행하는 한 줄 명령어:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
이후 터미널에서 아이디어를 작성하세요!
|
||||
|
||||
unstable 버전을 실행하려면 아래 명령어를 사용할 수도 있습니다:
|
||||
|
||||
```bash
|
||||
python run_flow.py
|
||||
```
|
||||
|
||||
## 기여 방법
|
||||
|
||||
모든 친절한 제안과 유용한 기여를 환영합니다! 이슈를 생성하거나 풀 리퀘스트를 제출해 주세요.
|
||||
|
||||
또는 📧 메일로 연락주세요. @mannaandpoem : mannaandpoem@gmail.com
|
||||
|
||||
## 커뮤니티 그룹
|
||||
Feishu 네트워킹 그룹에 참여하여 다른 개발자들과 경험을 공유하세요!
|
||||
|
||||
<div align="center" style="display: flex; gap: 20px;">
|
||||
<img src="assets/community_group.jpg" alt="OpenManus 交流群" width="300" />
|
||||
</div>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#mannaandpoem/OpenManus&Date)
|
||||
|
||||
## 감사의 글
|
||||
|
||||
이 프로젝트에 기본적인 지원을 제공해 주신 [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)에 깊은 감사를 드립니다.
|
||||
|
||||
OpenManus는 MetaGPT 기여자들에 의해 개발되었습니다. 이 에이전트 커뮤니티에 깊은 감사를 전합니다!
|
||||
|
||||
## 인용
|
||||
```bibtex
|
||||
@misc{openmanus2025,
|
||||
author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong},
|
||||
title = {OpenManus: An open-source framework for building general AI agents},
|
||||
year = {2025},
|
||||
publisher = {GitHub},
|
||||
journal = {GitHub repository},
|
||||
howpublished = {\url{https://github.com/mannaandpoem/OpenManus}},
|
||||
}
|
||||
```
|
24
README_zh.md
24
README_zh.md
@ -1,4 +1,7 @@
|
||||
[English](README.md) | 中文
|
||||
|
||||
[English](README.md) | 中文 | [한국어](README_ko.md) | [日本語](README_ja.md)
|
||||
|
||||
|
||||
|
||||
[](https://github.com/mannaandpoem/OpenManus/stargazers)
|
||||
 
|
||||
@ -9,8 +12,8 @@
|
||||
|
||||
Manus 非常棒,但 OpenManus 无需邀请码即可实现任何创意 🛫!
|
||||
|
||||
我们的团队成员 [@mannaandpoem](https://github.com/mannaandpoem) [@XiangJinyu](https://github.com/XiangJinyu) [@MoshiQAQ](https://github.com/MoshiQAQ) [@didiforgithub](https://github.com/didiforgithub) https://github.com/stellaHSR 来自 [@MetaGPT](https://github.com/geekan/MetaGPT) 组织,我们在 3
|
||||
小时内完成了原型开发并持续迭代中!
|
||||
我们的团队成员 [@Xinbin Liang](https://github.com/mannaandpoem) 和 [@Jinyu Xiang](https://github.com/XiangJinyu)(核心作者),以及 [@Zhaoyang Yu](https://github.com/MoshiQAQ)、[@Jiayi Zhang](https://github.com/didiforgithub) 和 [@Sirui Hong](https://github.com/stellaHSR),来自 [@MetaGPT](https://github.com/geekan/MetaGPT)团队。我们在 3
|
||||
小时内完成了开发并持续迭代中!
|
||||
|
||||
这是一个简洁的实现方案,欢迎任何建议、贡献和反馈!
|
||||
|
||||
@ -145,4 +148,19 @@ 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).
|
||||
|
||||
OpenManus 由 MetaGPT 社区的贡献者共同构建,感谢这个充满活力的智能体开发者社区!
|
||||
|
||||
## 引用我们
|
||||
|
||||
```bibtex
|
||||
@misc{openmanus2025,
|
||||
author = {Xinbin Liang and Jinyu Xiang and Zhaoyang Yu and Jiayi Zhang and Sirui Hong},
|
||||
title = {OpenManus: An open-source framework for building general AI agents},
|
||||
year = {2025},
|
||||
publisher = {GitHub},
|
||||
journal = {GitHub repository},
|
||||
howpublished = {\url{https://github.com/mannaandpoem/OpenManus}},
|
||||
}
|
||||
```
|
||||
|
@ -1,12 +1,12 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Literal, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from app.llm import LLM
|
||||
from app.logger import logger
|
||||
from app.schema import AgentState, Memory, Message
|
||||
from app.schema import AgentState, Memory, Message, ROLE_TYPE
|
||||
|
||||
|
||||
class BaseAgent(BaseModel, ABC):
|
||||
@ -82,7 +82,7 @@ class BaseAgent(BaseModel, ABC):
|
||||
|
||||
def update_memory(
|
||||
self,
|
||||
role: Literal["user", "system", "assistant", "tool"],
|
||||
role: ROLE_TYPE, # type: ignore
|
||||
content: str,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
@ -144,6 +144,8 @@ class BaseAgent(BaseModel, ABC):
|
||||
results.append(f"Step {self.current_step}: {step_result}")
|
||||
|
||||
if self.current_step >= self.max_steps:
|
||||
self.current_step = 0
|
||||
self.state = AgentState.IDLE
|
||||
results.append(f"Terminated: Reached max steps ({self.max_steps})")
|
||||
|
||||
return "\n".join(results) if results else "No steps executed"
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from app.agent.toolcall import ToolCallAgent
|
||||
@ -26,9 +28,16 @@ class Manus(ToolCallAgent):
|
||||
system_prompt: str = SYSTEM_PROMPT
|
||||
next_step_prompt: str = NEXT_STEP_PROMPT
|
||||
|
||||
max_observe: int = 2000
|
||||
max_steps: int = 20
|
||||
|
||||
# Add general-purpose tools to the tool collection
|
||||
available_tools: ToolCollection = Field(
|
||||
default_factory=lambda: ToolCollection(
|
||||
PythonExecute(), GoogleSearch(), BrowserUseTool(), FileSaver(), Terminate()
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -1,12 +1,12 @@
|
||||
import time
|
||||
from typing import Dict, List, Literal, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
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, ToolCall
|
||||
from app.schema import Message, TOOL_CHOICE_TYPE, 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: Literal["none", "auto", "required"] = "auto"
|
||||
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)
|
||||
@ -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="required",
|
||||
tool_choice=ToolChoice.REQUIRED,
|
||||
)
|
||||
assistant_msg = Message.from_tool_calls(
|
||||
content=response.content, tool_calls=response.tool_calls
|
||||
|
@ -1,12 +1,13 @@
|
||||
import json
|
||||
from typing import Any, List, Literal
|
||||
|
||||
from typing import Any, List, Literal, 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
|
||||
from app.schema import AgentState, Message, ToolCall, TOOL_CHOICE_TYPE, ToolChoice
|
||||
from app.tool import CreateChatCompletion, Terminate, ToolCollection
|
||||
|
||||
|
||||
@ -25,12 +26,13 @@ class ToolCallAgent(ReActAgent):
|
||||
available_tools: ToolCollection = ToolCollection(
|
||||
CreateChatCompletion(), Terminate()
|
||||
)
|
||||
tool_choices: Literal["none", "auto", "required"] = "auto"
|
||||
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)
|
||||
|
||||
max_steps: int = 30
|
||||
max_observe: Optional[Union[int, bool]] = None
|
||||
|
||||
async def think(self) -> bool:
|
||||
"""Process current state and decide next actions using tools"""
|
||||
@ -61,7 +63,7 @@ class ToolCallAgent(ReActAgent):
|
||||
|
||||
try:
|
||||
# Handle different tool_choices modes
|
||||
if self.tool_choices == "none":
|
||||
if self.tool_choices == ToolChoice.NONE:
|
||||
if response.tool_calls:
|
||||
logger.warning(
|
||||
f"🤔 Hmm, {self.name} tried to use tools when they weren't available!"
|
||||
@ -81,11 +83,11 @@ class ToolCallAgent(ReActAgent):
|
||||
)
|
||||
self.memory.add_message(assistant_msg)
|
||||
|
||||
if self.tool_choices == "required" and not self.tool_calls:
|
||||
if self.tool_choices == ToolChoice.REQUIRED and not self.tool_calls:
|
||||
return True # Will be handled in act()
|
||||
|
||||
# For 'auto' mode, continue with content if no commands but content exists
|
||||
if self.tool_choices == "auto" and not self.tool_calls:
|
||||
if self.tool_choices == ToolChoice.AUTO and not self.tool_calls:
|
||||
return bool(response.content)
|
||||
|
||||
return bool(self.tool_calls)
|
||||
@ -101,7 +103,7 @@ class ToolCallAgent(ReActAgent):
|
||||
async def act(self) -> str:
|
||||
"""Execute tool calls and handle their results"""
|
||||
if not self.tool_calls:
|
||||
if self.tool_choices == "required":
|
||||
if self.tool_choices == ToolChoice.REQUIRED:
|
||||
raise ValueError(TOOL_CALL_REQUIRED)
|
||||
|
||||
# Return last message content if no tool calls
|
||||
@ -110,6 +112,10 @@ class ToolCallAgent(ReActAgent):
|
||||
results = []
|
||||
for command in self.tool_calls:
|
||||
result = await self.execute_tool(command)
|
||||
|
||||
if self.max_observe:
|
||||
result = result[: self.max_observe]
|
||||
|
||||
logger.info(
|
||||
f"🎯 Tool '{command.function.name}' completed its mission! Result: {result}"
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import threading
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@ -25,8 +25,42 @@ class LLMSettings(BaseModel):
|
||||
api_version: str = Field(..., description="Azure Openai version if AzureOpenai")
|
||||
|
||||
|
||||
class ProxySettings(BaseModel):
|
||||
server: str = Field(None, description="Proxy server address")
|
||||
username: Optional[str] = Field(None, description="Proxy username")
|
||||
password: Optional[str] = Field(None, description="Proxy password")
|
||||
|
||||
|
||||
class BrowserSettings(BaseModel):
|
||||
headless: bool = Field(False, description="Whether to run browser in headless mode")
|
||||
disable_security: bool = Field(
|
||||
True, description="Disable browser security features"
|
||||
)
|
||||
extra_chromium_args: List[str] = Field(
|
||||
default_factory=list, description="Extra arguments to pass to the browser"
|
||||
)
|
||||
chrome_instance_path: Optional[str] = Field(
|
||||
None, description="Path to a Chrome instance to use"
|
||||
)
|
||||
wss_url: Optional[str] = Field(
|
||||
None, description="Connect to a browser instance via WebSocket"
|
||||
)
|
||||
cdp_url: Optional[str] = Field(
|
||||
None, description="Connect to a browser instance via CDP"
|
||||
)
|
||||
proxy: Optional[ProxySettings] = Field(
|
||||
None, description="Proxy settings for the browser"
|
||||
)
|
||||
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
llm: Dict[str, LLMSettings]
|
||||
browser_config: Optional[BrowserSettings] = Field(
|
||||
None, description="Browser configuration"
|
||||
)
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class Config:
|
||||
@ -82,6 +116,39 @@ class Config:
|
||||
"api_version": base_llm.get("api_version", ""),
|
||||
}
|
||||
|
||||
# handle browser config.
|
||||
browser_config = raw_config.get("browser", {})
|
||||
browser_settings = None
|
||||
|
||||
if browser_config:
|
||||
# handle proxy settings.
|
||||
proxy_config = browser_config.get("proxy", {})
|
||||
proxy_settings = None
|
||||
|
||||
if proxy_config and proxy_config.get("server"):
|
||||
proxy_settings = ProxySettings(
|
||||
**{
|
||||
k: v
|
||||
for k, v in proxy_config.items()
|
||||
if k in ["server", "username", "password"] and v
|
||||
}
|
||||
)
|
||||
|
||||
# filter valid browser config parameters.
|
||||
valid_browser_params = {
|
||||
k: v
|
||||
for k, v in browser_config.items()
|
||||
if k in BrowserSettings.__annotations__ and v is not None
|
||||
}
|
||||
|
||||
# if there is proxy settings, add it to the parameters.
|
||||
if proxy_settings:
|
||||
valid_browser_params["proxy"] = proxy_settings
|
||||
|
||||
# only create BrowserSettings when there are valid parameters.
|
||||
if valid_browser_params:
|
||||
browser_settings = BrowserSettings(**valid_browser_params)
|
||||
|
||||
config_dict = {
|
||||
"llm": {
|
||||
"default": default_settings,
|
||||
@ -89,7 +156,8 @@ class Config:
|
||||
name: {**default_settings, **override_config}
|
||||
for name, override_config in llm_overrides.items()
|
||||
},
|
||||
}
|
||||
},
|
||||
"browser_config": browser_settings,
|
||||
}
|
||||
|
||||
self._config = AppConfig(**config_dict)
|
||||
@ -98,5 +166,9 @@ class Config:
|
||||
def llm(self) -> Dict[str, LLMSettings]:
|
||||
return self._config.llm
|
||||
|
||||
@property
|
||||
def browser_config(self) -> Optional[BrowserSettings]:
|
||||
return self._config.browser_config
|
||||
|
||||
|
||||
config = Config()
|
||||
|
@ -8,7 +8,7 @@ from app.agent.base import BaseAgent
|
||||
from app.flow.base import BaseFlow, PlanStepStatus
|
||||
from app.llm import LLM
|
||||
from app.logger import logger
|
||||
from app.schema import AgentState, Message
|
||||
from app.schema import AgentState, Message, ToolChoice
|
||||
from app.tool import PlanningTool
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ class PlanningFlow(BaseFlow):
|
||||
messages=[user_message],
|
||||
system_msgs=[system_message],
|
||||
tools=[self.planning_tool.to_param()],
|
||||
tool_choice="required",
|
||||
tool_choice=ToolChoice.REQUIRED,
|
||||
)
|
||||
|
||||
# Process tool calls if present
|
||||
|
12
app/llm.py
12
app/llm.py
@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Literal, Optional, Union
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from openai import (
|
||||
APIError,
|
||||
@ -12,7 +12,7 @@ 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
|
||||
from app.schema import Message, TOOL_CHOICE_TYPE, ROLE_VALUES, TOOL_CHOICE_VALUES, ToolChoice
|
||||
|
||||
|
||||
class LLM:
|
||||
@ -88,7 +88,7 @@ class LLM:
|
||||
|
||||
# Validate all messages have required fields
|
||||
for msg in formatted_messages:
|
||||
if msg["role"] not in ["system", "user", "assistant", "tool"]:
|
||||
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(
|
||||
@ -185,9 +185,9 @@ class LLM:
|
||||
self,
|
||||
messages: List[Union[dict, Message]],
|
||||
system_msgs: Optional[List[Union[dict, Message]]] = None,
|
||||
timeout: int = 60,
|
||||
timeout: int = 300,
|
||||
tools: Optional[List[dict]] = None,
|
||||
tool_choice: Literal["none", "auto", "required"] = "auto",
|
||||
tool_choice: TOOL_CHOICE_TYPE = ToolChoice.AUTO, # type: ignore
|
||||
temperature: Optional[float] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@ -213,7 +213,7 @@ class LLM:
|
||||
"""
|
||||
try:
|
||||
# Validate tool_choice
|
||||
if tool_choice not in ["none", "auto", "required"]:
|
||||
if tool_choice not in TOOL_CHOICE_VALUES:
|
||||
raise ValueError(f"Invalid tool_choice: {tool_choice}")
|
||||
|
||||
# Format messages
|
||||
|
@ -10,5 +10,9 @@ BrowserUseTool: Open, browse, and use web browsers.If you open a local HTML file
|
||||
|
||||
GoogleSearch: Perform web information retrieval
|
||||
|
||||
Terminate: End the current interaction when the task is complete or when you need additional information from the user. Use this tool to signal that you've finished addressing the user's request or need clarification before proceeding further.
|
||||
|
||||
Based on user needs, proactively select the most appropriate tool or combination of tools. For complex tasks, you can break down the problem and use different tools step by step to solve it. After using each tool, clearly explain the execution results and suggest the next steps.
|
||||
|
||||
Always maintain a helpful, informative tone throughout the interaction. If you encounter any limitations or need more details, clearly communicate this to the user before terminating.
|
||||
"""
|
||||
|
@ -3,6 +3,24 @@ 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"
|
||||
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"""
|
||||
@ -29,7 +47,7 @@ class ToolCall(BaseModel):
|
||||
class Message(BaseModel):
|
||||
"""Represents a chat message in the conversation"""
|
||||
|
||||
role: Literal["system", "user", "assistant", "tool"] = Field(...)
|
||||
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)
|
||||
@ -71,22 +89,22 @@ class Message(BaseModel):
|
||||
@classmethod
|
||||
def user_message(cls, content: str) -> "Message":
|
||||
"""Create a user message"""
|
||||
return cls(role="user", content=content)
|
||||
return cls(role=Role.USER, content=content)
|
||||
|
||||
@classmethod
|
||||
def system_message(cls, content: str) -> "Message":
|
||||
"""Create a system message"""
|
||||
return cls(role="system", content=content)
|
||||
return cls(role=Role.SYSTEM, content=content)
|
||||
|
||||
@classmethod
|
||||
def assistant_message(cls, content: Optional[str] = None) -> "Message":
|
||||
"""Create an assistant message"""
|
||||
return cls(role="assistant", content=content)
|
||||
return cls(role=Role.ASSISTANT, content=content)
|
||||
|
||||
@classmethod
|
||||
def tool_message(cls, content: str, name, tool_call_id: str) -> "Message":
|
||||
"""Create a tool message"""
|
||||
return cls(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(
|
||||
@ -103,7 +121,7 @@ class Message(BaseModel):
|
||||
for call in tool_calls
|
||||
]
|
||||
return cls(
|
||||
role="assistant", content=content, tool_calls=formatted_calls, **kwargs
|
||||
role=Role.ASSISTANT, content=content, tool_calls=formatted_calls, **kwargs
|
||||
)
|
||||
|
||||
|
||||
|
@ -4,14 +4,17 @@ from typing import Optional
|
||||
|
||||
from browser_use import Browser as BrowserUseBrowser
|
||||
from browser_use import BrowserConfig
|
||||
from browser_use.browser.context import BrowserContext
|
||||
from browser_use.browser.context import BrowserContext, BrowserContextConfig
|
||||
from browser_use.dom.service import DomService
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from app.config import config
|
||||
from app.tool.base import BaseTool, ToolResult
|
||||
|
||||
|
||||
MAX_LENGTH = 2000
|
||||
|
||||
_BROWSER_DESCRIPTION = """
|
||||
Interact with a web browser to perform various actions such as navigation, element interaction,
|
||||
content extraction, and tab management. Supported actions include:
|
||||
@ -103,10 +106,50 @@ class BrowserUseTool(BaseTool):
|
||||
async def _ensure_browser_initialized(self) -> BrowserContext:
|
||||
"""Ensure browser and context are initialized."""
|
||||
if self.browser is None:
|
||||
self.browser = BrowserUseBrowser(BrowserConfig(headless=False))
|
||||
browser_config_kwargs = {"headless": False}
|
||||
|
||||
if config.browser_config:
|
||||
from browser_use.browser.browser import ProxySettings
|
||||
|
||||
# handle proxy settings.
|
||||
if config.browser_config.proxy and config.browser_config.proxy.server:
|
||||
browser_config_kwargs["proxy"] = ProxySettings(
|
||||
server=config.browser_config.proxy.server,
|
||||
username=config.browser_config.proxy.username,
|
||||
password=config.browser_config.proxy.password,
|
||||
)
|
||||
|
||||
browser_attrs = [
|
||||
"headless",
|
||||
"disable_security",
|
||||
"extra_chromium_args",
|
||||
"chrome_instance_path",
|
||||
"wss_url",
|
||||
"cdp_url",
|
||||
]
|
||||
|
||||
for attr in browser_attrs:
|
||||
value = getattr(config.browser_config, attr, None)
|
||||
if value is not None:
|
||||
if not isinstance(value, list) or value:
|
||||
browser_config_kwargs[attr] = value
|
||||
|
||||
self.browser = BrowserUseBrowser(BrowserConfig(**browser_config_kwargs))
|
||||
|
||||
if self.context is None:
|
||||
self.context = await self.browser.new_context()
|
||||
context_config = BrowserContextConfig()
|
||||
|
||||
# if there is context config in the config, use it.
|
||||
if (
|
||||
config.browser_config
|
||||
and hasattr(config.browser_config, "new_context_config")
|
||||
and config.browser_config.new_context_config
|
||||
):
|
||||
context_config = config.browser_config.new_context_config
|
||||
|
||||
self.context = await self.browser.new_context(context_config)
|
||||
self.dom_service = DomService(await self.context.get_current_page())
|
||||
|
||||
return self.context
|
||||
|
||||
async def execute(
|
||||
@ -180,7 +223,9 @@ class BrowserUseTool(BaseTool):
|
||||
|
||||
elif action == "get_html":
|
||||
html = await context.get_page_html()
|
||||
truncated = html[:2000] + "..." if len(html) > 2000 else html
|
||||
truncated = (
|
||||
html[:MAX_LENGTH] + "..." if len(html) > MAX_LENGTH else html
|
||||
)
|
||||
return ToolResult(output=truncated)
|
||||
|
||||
elif action == "get_text":
|
||||
|
182
app/tool/terminal.py
Normal file
182
app/tool/terminal.py
Normal file
@ -0,0 +1,182 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shlex
|
||||
from typing import Optional
|
||||
|
||||
from app.tool.base import BaseTool, CLIResult
|
||||
|
||||
|
||||
class Terminal(BaseTool):
|
||||
name: str = "execute_command"
|
||||
description: str = """Request to execute a CLI command on the system.
|
||||
Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task.
|
||||
You must tailor your command to the user's system and provide a clear explanation of what the command does.
|
||||
Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run.
|
||||
Commands will be executed in the current working directory.
|
||||
Note: You MUST append a `sleep 0.05` to the end of the command for commands that will complete in under 50ms, as this will circumvent a known issue with the terminal tool where it will sometimes not return the output when the command completes too quickly.
|
||||
"""
|
||||
parameters: dict = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "(required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.",
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
}
|
||||
process: Optional[asyncio.subprocess.Process] = None
|
||||
current_path: str = os.getcwd()
|
||||
lock: asyncio.Lock = asyncio.Lock()
|
||||
|
||||
async def execute(self, command: str) -> CLIResult:
|
||||
"""
|
||||
Execute a terminal command asynchronously with persistent context.
|
||||
|
||||
Args:
|
||||
command (str): The terminal command to execute.
|
||||
|
||||
Returns:
|
||||
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()]
|
||||
final_output = CLIResult(output="", error="")
|
||||
|
||||
for cmd in commands:
|
||||
sanitized_command = self._sanitize_command(cmd)
|
||||
|
||||
# Handle 'cd' command internally
|
||||
if sanitized_command.lstrip().startswith("cd "):
|
||||
result = await self._handle_cd_command(sanitized_command)
|
||||
else:
|
||||
async with self.lock:
|
||||
try:
|
||||
self.process = await asyncio.create_subprocess_shell(
|
||||
sanitized_command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=self.current_path,
|
||||
)
|
||||
stdout, stderr = await self.process.communicate()
|
||||
result = CLIResult(
|
||||
output=stdout.decode().strip(),
|
||||
error=stderr.decode().strip()
|
||||
)
|
||||
except Exception as e:
|
||||
result = CLIResult(output="", error=str(e))
|
||||
finally:
|
||||
self.process = None
|
||||
|
||||
# Combine outputs
|
||||
if 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
|
||||
|
||||
# Remove trailing newlines
|
||||
final_output.output = final_output.output.rstrip()
|
||||
final_output.error = final_output.error.rstrip()
|
||||
return final_output
|
||||
|
||||
async def execute_in_env(self, env_name: str, command: str) -> CLIResult:
|
||||
"""
|
||||
Execute a terminal command asynchronously within a specified Conda environment.
|
||||
|
||||
Args:
|
||||
env_name (str): The name of the Conda environment.
|
||||
command (str): The terminal command to execute within the environment.
|
||||
|
||||
Returns:
|
||||
str: The output, and error of the command execution.
|
||||
"""
|
||||
sanitized_command = self._sanitize_command(command)
|
||||
|
||||
# Construct the command to run within the Conda environment
|
||||
# Using 'conda run -n env_name command' to execute without activating
|
||||
conda_command = f"conda run -n {shlex.quote(env_name)} {sanitized_command}"
|
||||
|
||||
return await self.execute(conda_command)
|
||||
|
||||
async def _handle_cd_command(self, command: str) -> CLIResult:
|
||||
"""
|
||||
Handle 'cd' commands to change the current path.
|
||||
|
||||
Args:
|
||||
command (str): The 'cd' command to process.
|
||||
|
||||
Returns:
|
||||
TerminalOutput: The result of the 'cd' command.
|
||||
"""
|
||||
try:
|
||||
parts = shlex.split(command)
|
||||
if len(parts) < 2:
|
||||
new_path = os.path.expanduser("~")
|
||||
else:
|
||||
new_path = os.path.expanduser(parts[1])
|
||||
|
||||
# Handle relative paths
|
||||
if not os.path.isabs(new_path):
|
||||
new_path = os.path.join(self.current_path, new_path)
|
||||
|
||||
new_path = os.path.abspath(new_path)
|
||||
|
||||
if os.path.isdir(new_path):
|
||||
self.current_path = new_path
|
||||
return CLIResult(
|
||||
output=f"Changed directory to {self.current_path}",
|
||||
error=""
|
||||
)
|
||||
else:
|
||||
return CLIResult(
|
||||
output="",
|
||||
error=f"No such directory: {new_path}"
|
||||
)
|
||||
except Exception as e:
|
||||
return CLIResult(output="", error=str(e))
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_command(command: str) -> str:
|
||||
"""
|
||||
Sanitize the command for safe execution.
|
||||
|
||||
Args:
|
||||
command (str): The command to sanitize.
|
||||
|
||||
Returns:
|
||||
str: The sanitized command.
|
||||
"""
|
||||
# Example sanitization: restrict certain dangerous commands
|
||||
dangerous_commands = ["rm", "sudo", "shutdown", "reboot"]
|
||||
try:
|
||||
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:
|
||||
# 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.")
|
||||
|
||||
# Additional sanitization logic can be added here
|
||||
return command
|
||||
|
||||
async def close(self):
|
||||
"""Close the persistent shell process if it exists."""
|
||||
async with self.lock:
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
try:
|
||||
await asyncio.wait_for(self.process.wait(), timeout=5)
|
||||
except asyncio.TimeoutError:
|
||||
self.process.kill()
|
||||
await self.process.wait()
|
||||
finally:
|
||||
self.process = None
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Enter the asynchronous context manager."""
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Exit the asynchronous context manager and close the process."""
|
||||
await self.close()
|
@ -1,7 +1,8 @@
|
||||
from app.tool.base import BaseTool
|
||||
|
||||
|
||||
_TERMINATE_DESCRIPTION = """Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task."""
|
||||
_TERMINATE_DESCRIPTION = """Terminate the interaction when the request is met OR if the assistant cannot proceed further with the task.
|
||||
When you have finished all the tasks, call this tool to end the work."""
|
||||
|
||||
|
||||
class Terminate(BaseTool):
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 166 KiB |
@ -20,3 +20,25 @@ temperature = 0.0
|
||||
model = "claude-3-5-sonnet"
|
||||
base_url = "https://api.openai.com/v1"
|
||||
api_key = "sk-..."
|
||||
|
||||
# Optional configuration for specific browser configuration
|
||||
# [browser]
|
||||
# Whether to run browser in headless mode (default: false)
|
||||
#headless = false
|
||||
# Disable browser security features (default: true)
|
||||
#disable_security = true
|
||||
# Extra arguments to pass to the browser
|
||||
#extra_chromium_args = []
|
||||
# Path to a Chrome instance to use to connect to your normal browser
|
||||
# e.g. '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
||||
#chrome_instance_path = ""
|
||||
# Connect to a browser instance via WebSocket
|
||||
#wss_url = ""
|
||||
# Connect to a browser instance via CDP
|
||||
#cdp_url = ""
|
||||
|
||||
# Optional configuration, Proxy settings for the browser
|
||||
# [browser.proxy]
|
||||
# server = "http://proxy-server:port"
|
||||
# username = "proxy-username"
|
||||
# password = "proxy-password"
|
||||
|
26
main.py
26
main.py
@ -6,21 +6,17 @@ from app.logger import logger
|
||||
|
||||
async def main():
|
||||
agent = Manus()
|
||||
while True:
|
||||
try:
|
||||
prompt = input("Enter your prompt (or 'exit'/'quit' to quit): ")
|
||||
prompt_lower = prompt.lower()
|
||||
if prompt_lower in ["exit", "quit"]:
|
||||
logger.info("Goodbye!")
|
||||
break
|
||||
if not prompt.strip():
|
||||
logger.warning("Skipping empty prompt.")
|
||||
continue
|
||||
logger.warning("Processing your request...")
|
||||
await agent.run(prompt)
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("Goodbye!")
|
||||
break
|
||||
try:
|
||||
prompt = input("Enter your prompt: ")
|
||||
if not prompt.strip():
|
||||
logger.warning("Empty prompt provided.")
|
||||
return
|
||||
|
||||
logger.warning("Processing your request...")
|
||||
await agent.run(prompt)
|
||||
logger.info("Request processing completed.")
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("Operation interrupted.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
61
run_flow.py
61
run_flow.py
@ -12,41 +12,38 @@ async def run_flow():
|
||||
"manus": Manus(),
|
||||
}
|
||||
|
||||
while True:
|
||||
try:
|
||||
prompt = input("Enter your prompt: ")
|
||||
|
||||
if prompt.strip().isspace() or not prompt:
|
||||
logger.warning("Empty prompt provided.")
|
||||
return
|
||||
|
||||
flow = FlowFactory.create_flow(
|
||||
flow_type=FlowType.PLANNING,
|
||||
agents=agents,
|
||||
)
|
||||
logger.warning("Processing your request...")
|
||||
|
||||
try:
|
||||
prompt = input("Enter your prompt (or 'exit' to quit): ")
|
||||
if prompt.lower() == "exit":
|
||||
logger.info("Goodbye!")
|
||||
break
|
||||
|
||||
flow = FlowFactory.create_flow(
|
||||
flow_type=FlowType.PLANNING,
|
||||
agents=agents,
|
||||
start_time = time.time()
|
||||
result = await asyncio.wait_for(
|
||||
flow.execute(prompt),
|
||||
timeout=3600, # 60 minute timeout for the entire execution
|
||||
)
|
||||
elapsed_time = time.time() - start_time
|
||||
logger.info(f"Request processed in {elapsed_time:.2f} seconds")
|
||||
logger.info(result)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("Request processing timed out after 1 hour")
|
||||
logger.info(
|
||||
"Operation terminated due to timeout. Please try a simpler request."
|
||||
)
|
||||
if prompt.strip().isspace():
|
||||
logger.warning("Skipping empty prompt.")
|
||||
continue
|
||||
logger.warning("Processing your request...")
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
result = await asyncio.wait_for(
|
||||
flow.execute(prompt),
|
||||
timeout=3600, # 60 minute timeout for the entire execution
|
||||
)
|
||||
elapsed_time = time.time() - start_time
|
||||
logger.info(f"Request processed in {elapsed_time:.2f} seconds")
|
||||
logger.info(result)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("Request processing timed out after 1 hour")
|
||||
logger.info(
|
||||
"Operation terminated due to timeout. Please try a simpler request."
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Operation cancelled by user.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {str(e)}")
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Operation cancelled by user.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
x
Reference in New Issue
Block a user