From f1bcfc7868db021ad1780bed41b4f04271e1b2c4 Mon Sep 17 00:00:00 2001 From: better629 Date: Fri, 27 Oct 2023 17:06:28 +0800 Subject: [PATCH 01/53] add docs/tutorial --- docs/tutorial/faq.md | 6 +++ docs/tutorial/usage.md | 67 +++++++++++++++++++++++++++++++ docs/tutorial/usage_cn.md | 63 +++++++++++++++++++++++++++++ docs/tutorial/what_can_this_do.md | 0 4 files changed, 136 insertions(+) create mode 100644 docs/tutorial/faq.md create mode 100644 docs/tutorial/usage.md create mode 100644 docs/tutorial/usage_cn.md create mode 100644 docs/tutorial/what_can_this_do.md diff --git a/docs/tutorial/faq.md b/docs/tutorial/faq.md new file mode 100644 index 000000000..0d2fa7ef0 --- /dev/null +++ b/docs/tutorial/faq.md @@ -0,0 +1,6 @@ +## FAQs + +### installation + + +### openai usage diff --git a/docs/tutorial/usage.md b/docs/tutorial/usage.md new file mode 100644 index 000000000..ee87b65c9 --- /dev/null +++ b/docs/tutorial/usage.md @@ -0,0 +1,67 @@ +## MetaGPT Usage + +### Configuration + +- Configure your `OPENAI_API_KEY` in any of `config/key.yaml / config/config.yaml / env` +- Priority order: `config/key.yaml > config/config.yaml > env` + +```bash +# Copy the configuration file and make the necessary modifications. +cp config/config.yaml config/key.yaml +``` + +| Variable Name | config/key.yaml | env | +| ------------------------------------------ | ----------------------------------------- | ----------------------------------------------- | +| OPENAI_API_KEY # Replace with your own key | OPENAI_API_KEY: "sk-..." | export OPENAI_API_KEY="sk-..." | +| OPENAI_API_BASE # Optional | OPENAI_API_BASE: "https:///v1" | export OPENAI_API_BASE="https:///v1" | + +### Initiating a startup + +```shell +# Run the script +python startup.py "Write a cli snake game" +# Do not hire an engineer to implement the project +python startup.py "Write a cli snake game" --implement False +# Hire an engineer and perform code reviews +python startup.py "Write a cli snake game" --code_review True +``` + +After running the script, you can find your new project in the `workspace/` directory. + +### Preference of Platform or Tool + +You can tell which platform or tool you want to use when stating your requirements. + +```shell +python startup.py "Write a cli snake game based on pygame" +``` + +### Usage + +``` +NAME + startup.py - We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. + +SYNOPSIS + startup.py IDEA + +DESCRIPTION + We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. + +POSITIONAL ARGUMENTS + IDEA + Type: str + Your innovative idea, such as "Creating a snake game." + +FLAGS + --investment=INVESTMENT + Type: float + Default: 3.0 + As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. + --n_round=N_ROUND + Type: int + Default: 5 + +NOTES + You can also use flags syntax for POSITIONAL ARGUMENTS +``` \ No newline at end of file diff --git a/docs/tutorial/usage_cn.md b/docs/tutorial/usage_cn.md new file mode 100644 index 000000000..4b3bdd2c3 --- /dev/null +++ b/docs/tutorial/usage_cn.md @@ -0,0 +1,63 @@ +## MetaGPT 使用 + +### 配置 + +- 在 `config/key.yaml / config/config.yaml / env` 中配置您的 `OPENAI_API_KEY` +- 优先级顺序:`config/key.yaml > config/config.yaml > env` + +```bash +# 复制配置文件并进行必要的修改 +cp config/config.yaml config/key.yaml +``` + +| 变量名 | config/key.yaml | env | +| ----------------------------------- | ----------------------------------------- | ----------------------------------------------- | +| OPENAI_API_KEY # 用您自己的密钥替换 | OPENAI_API_KEY: "sk-..." | export OPENAI_API_KEY="sk-..." | +| OPENAI_API_BASE # 可选 | OPENAI_API_BASE: "https:///v1" | export OPENAI_API_BASE="https:///v1" | + +### 示例:启动一个创业公司 + +```shell +python startup.py "写一个命令行贪吃蛇" +# 开启code review模式会花费更多的金钱, 但是会提升代码质量和成功率 +python startup.py "写一个命令行贪吃蛇" --code_review True +``` + +运行脚本后,您可以在 `workspace/` 目录中找到您的新项目。 + +### 平台或工具的倾向性 +可以在阐述需求时说明想要使用的平台或工具。 +例如: +```shell +python startup.py "写一个基于pygame的命令行贪吃蛇" +``` + +### 使用 + +``` +名称 + startup.py - 我们是一家AI软件创业公司。通过投资我们,您将赋能一个充满无限可能的未来。 + +概要 + startup.py IDEA + +描述 + 我们是一家AI软件创业公司。通过投资我们,您将赋能一个充满无限可能的未来。 + +位置参数 + IDEA + 类型: str + 您的创新想法,例如"写一个命令行贪吃蛇。" + +标志 + --investment=INVESTMENT + 类型: float + 默认值: 3.0 + 作为投资者,您有机会向这家AI公司投入一定的美元金额。 + --n_round=N_ROUND + 类型: int + 默认值: 5 + +备注 + 您也可以用`标志`的语法,来处理`位置参数` +``` diff --git a/docs/tutorial/what_can_this_do.md b/docs/tutorial/what_can_this_do.md new file mode 100644 index 000000000..e69de29bb From 11e720a496cf3d63d19bc6f9a606113b02ecd7cc Mon Sep 17 00:00:00 2001 From: better629 Date: Fri, 27 Oct 2023 17:06:45 +0800 Subject: [PATCH 02/53] add docs/install --- docs/install/cli_install.md | 109 ++++++++++++++++++++++++++++++ docs/install/cli_install_cn.md | 43 ++++++++++++ docs/install/docker_install.md | 44 ++++++++++++ docs/install/docker_install_cn.md | 44 ++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 docs/install/cli_install.md create mode 100644 docs/install/cli_install_cn.md create mode 100644 docs/install/docker_install.md create mode 100644 docs/install/docker_install_cn.md diff --git a/docs/install/cli_install.md b/docs/install/cli_install.md new file mode 100644 index 000000000..19f0e3c75 --- /dev/null +++ b/docs/install/cli_install.md @@ -0,0 +1,109 @@ +## Traditional Command Line Installation + +### Support System and version +| System Version | Python Version | Supported | +| ---- | ---- | ----- | +| macOS 13.x | python 3.9 | Yes | +| Windows 11 | python 3.9 | Yes | +| Ubuntu 22.04 | python 3.9 | Yes | + +### Detail Installation +```bash +# Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) +npm --version +sudo npm install -g @mermaid-js/mermaid-cli + +# Step 2: Ensure that Python 3.9+ is installed on your system. You can check this by using: +python3 --version + +# Step 3: Clone the repository to your local machine, and install it. +git clone https://github.com/geekan/metagpt +cd metagpt +pip install -e. +``` + +**Note:** + +- If already have Chrome, Chromium, or MS Edge installed, you can skip downloading Chromium by setting the environment variable + `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` to `true`. + +- Some people are [having issues](https://github.com/mermaidjs/mermaid.cli/issues/15) installing this tool globally. Installing it locally is an alternative solution, + + ```bash + npm install @mermaid-js/mermaid-cli + ``` + +- don't forget to the configuration for mmdc in config.yml + + ```yml + PUPPETEER_CONFIG: "./config/puppeteer-config.json" + MMDC: "./node_modules/.bin/mmdc" + ``` + +- if `pip install -e.` fails with error `[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`, try instead running `pip install -e. --user` + +- To convert Mermaid charts to SVG, PNG, and PDF formats. In addition to the Node.js version of Mermaid-CLI, you now have the option to use Python version Playwright, pyppeteer or mermaid.ink for this task. + + - Playwright + - **Install Playwright** + + ```bash + pip install playwright + ``` + + - **Install the Required Browsers** + + to support PDF conversion, please install Chrominum. + + ```bash + playwright install --with-deps chromium + ``` + + - **modify `config.yaml`** + + uncomment MERMAID_ENGINE from config.yaml and change it to `playwright` + + ```yaml + MERMAID_ENGINE: playwright + ``` + + - pyppeteer + - **Install pyppeteer** + + ```bash + pip install pyppeteer + ``` + + - **Use your own Browsers** + + pyppeteer allows you use installed browsers, please set the following envirment + + ```bash + export PUPPETEER_EXECUTABLE_PATH = /path/to/your/chromium or edge or chrome + ``` + + please do not use this command to install browser, it is too old + + ```bash + pyppeteer-install + ``` + + - **modify `config.yaml`** + + uncomment MERMAID_ENGINE from config.yaml and change it to `pyppeteer` + + ```yaml + MERMAID_ENGINE: pyppeteer + ``` + + - mermaid.ink + - **modify `config.yaml`** + + uncomment MERMAID_ENGINE from config.yaml and change it to `ink` + + ```yaml + MERMAID_ENGINE: ink + ``` + + Note: this method does not support pdf export. + \ No newline at end of file diff --git a/docs/install/cli_install_cn.md b/docs/install/cli_install_cn.md new file mode 100644 index 000000000..fe97a0b80 --- /dev/null +++ b/docs/install/cli_install_cn.md @@ -0,0 +1,43 @@ +## 命令行安装 + +### 支持的系统和版本 +| 系统版本 | Python 版本 | 是否支持 | +| ---- | ---- | ----- | +| macOS 13.x | python 3.9 | 是 | +| Windows 11 | python 3.9 | 是 | +| Ubuntu 22.04 | python 3.9 | 是 | + +### 详细安装 + +```bash +# 第 1 步:确保您的系统上安装了 NPM。并使用npm安装mermaid-js +npm --version +sudo npm install -g @mermaid-js/mermaid-cli + +# 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下命令进行检查: +python --version + +# 第 3 步:克隆仓库到您的本地机器,并进行安装。 +git clone https://github.com/geekan/metagpt +cd metagpt +pip install -e. +``` + +**注意:** + +- 如果已经安装了Chrome、Chromium或MS Edge,可以通过将环境变量`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`设置为`true`来跳过下载Chromium。 + +- 一些人在全局安装此工具时遇到问题。在本地安装是替代解决方案, + + ```bash + npm install @mermaid-js/mermaid-cli + ``` + +- 不要忘记在config.yml中为mmdc配置配置, + + ```yml + PUPPETEER_CONFIG: "./config/puppeteer-config.json" + MMDC: "./node_modules/.bin/mmdc" + ``` + +- 如果`pip install -e.`失败并显示错误`[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`,请尝试使用`pip install -e. --user`运行。 diff --git a/docs/install/docker_install.md b/docs/install/docker_install.md new file mode 100644 index 000000000..b803a5dae --- /dev/null +++ b/docs/install/docker_install.md @@ -0,0 +1,44 @@ +## Docker Installation + +### Use default MetaGPT image + +```bash +# Step 1: Download metagpt official image and prepare config.yaml +docker pull metagpt/metagpt:latest +mkdir -p /opt/metagpt/{config,workspace} +docker run --rm metagpt/metagpt:latest cat /app/metagpt/config/config.yaml > /opt/metagpt/config/key.yaml +vim /opt/metagpt/config/key.yaml # Change the config + +# Step 2: Run metagpt demo with container +docker run --rm \ + --privileged \ + -v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \ + -v /opt/metagpt/workspace:/app/metagpt/workspace \ + metagpt/metagpt:latest \ + python3 startup.py "Write a cli snake game" + +# You can also start a container and execute commands in it +docker run --name metagpt -d \ + --privileged \ + -v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \ + -v /opt/metagpt/workspace:/app/metagpt/workspace \ + metagpt/metagpt:latest + +docker exec -it metagpt /bin/bash +$ python3 startup.py "Write a cli snake game" +``` + +The command `docker run ...` do the following things: + +- Run in privileged mode to have permission to run the browser +- Map host configure file `/opt/metagpt/config/key.yaml` to container `/app/metagpt/config/key.yaml` +- Map host directory `/opt/metagpt/workspace` to container `/app/metagpt/workspace` +- Execute the demo command `python3 startup.py "Write a cli snake game"` + +### Build image by yourself + +```bash +# You can also build metagpt image by yourself. +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT && docker build -t metagpt:custom . +``` diff --git a/docs/install/docker_install_cn.md b/docs/install/docker_install_cn.md new file mode 100644 index 000000000..347fae10c --- /dev/null +++ b/docs/install/docker_install_cn.md @@ -0,0 +1,44 @@ +## Docker安装 + +### 使用MetaGPT镜像 + +```bash +# 步骤1: 下载metagpt官方镜像并准备好config.yaml +docker pull metagpt/metagpt:latest +mkdir -p /opt/metagpt/{config,workspace} +docker run --rm metagpt/metagpt:latest cat /app/metagpt/config/config.yaml > /opt/metagpt/config/key.yaml +vim /opt/metagpt/config/key.yaml # 修改配置文件 + +# 步骤2: 使用容器运行metagpt演示 +docker run --rm \ + --privileged \ + -v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \ + -v /opt/metagpt/workspace:/app/metagpt/workspace \ + metagpt/metagpt:latest \ + python startup.py "Write a cli snake game" + +# 您也可以启动一个容器并在其中执行命令 +docker run --name metagpt -d \ + --privileged \ + -v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \ + -v /opt/metagpt/workspace:/app/metagpt/workspace \ + metagpt/metagpt:latest + +docker exec -it metagpt /bin/bash +$ python startup.py "Write a cli snake game" +``` + +`docker run ...`做了以下事情: + +- 以特权模式运行,有权限运行浏览器 +- 将主机文件 `/opt/metagpt/config/key.yaml` 映射到容器文件 `/app/metagpt/config/key.yaml` +- 将主机目录 `/opt/metagpt/workspace` 映射到容器目录 `/app/metagpt/workspace` +- 执行示例命令 `python startup.py "Write a cli snake game"` + +### 自己构建镜像 + +```bash +# 您也可以自己构建metagpt镜像 +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT && docker build -t metagpt:custom . +``` From d62cf5cf1d68881d10bb11b5adf2f594a3d3589f Mon Sep 17 00:00:00 2001 From: better629 Date: Fri, 27 Oct 2023 17:08:25 +0800 Subject: [PATCH 03/53] update EN&CN README --- README.md | 280 ++++++---------------------------------- docs/README_CN.md | 194 +++++++--------------------- docs/examples/README.md | 0 3 files changed, 88 insertions(+), 386 deletions(-) create mode 100644 docs/examples/README.md diff --git a/README.md b/README.md index 70460ceb4..f3fb3dff9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ # MetaGPT: The Multi-Agent Framework CN doc EN doc JA doc -Discord Follow +Discord Follow License: MIT roadmap Twitter Follow @@ -33,31 +33,9 @@ # MetaGPT: The Multi-Agent Framework

Software Company Multi-Role Schematic (Gradually Implementing)

-## MetaGPT's Abilities +## Install - -https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419 - - - -## Examples (fully generated by GPT-4) - -For example, if you type `python startup.py "Design a RecSys like Toutiao"`, you would get many outputs, one of them is data & api design - -![Jinri Toutiao Recsys Data & API Design](docs/resources/workspace/content_rec_sys/resources/data_api_design.png) - -It costs approximately **$0.2** (in GPT-4 API fees) to generate one example with analysis and design, and around **$2.0** for a full project. - - - - -## Installation - -### Installation Video Guide - -- [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) - -### Traditional Installation +### Conda installation ```bash # Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) @@ -65,100 +43,24 @@ # Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. sudo npm install -g @mermaid-js/mermaid-cli # Step 2: Ensure that Python 3.9+ is installed on your system. You can check this by using: -python --version +# You can use conda to initialize a new python env +# conda create -n metagpt python=3.9 +# conda activate metagpt +python3 --version # Step 3: Clone the repository to your local machine, and install it. git clone https://github.com/geekan/metagpt cd metagpt -pip install -e. +pip3 install -e. + +# Step 4: run the startup.py +# setup your OPENAI_API_KEY in key.yaml copy from config.yaml +python3 startup.py "Write a cli snake game" ``` -**Note:** +detail installation please refer to [cli_install](docs/install/cli_install.md) -- If already have Chrome, Chromium, or MS Edge installed, you can skip downloading Chromium by setting the environment variable - `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` to `true`. - -- Some people are [having issues](https://github.com/mermaidjs/mermaid.cli/issues/15) installing this tool globally. Installing it locally is an alternative solution, - - ```bash - npm install @mermaid-js/mermaid-cli - ``` - -- don't forget to the configuration for mmdc in config.yml - - ```yml - PUPPETEER_CONFIG: "./config/puppeteer-config.json" - MMDC: "./node_modules/.bin/mmdc" - ``` - -- if `pip install -e.` fails with error `[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`, try instead running `pip install -e. --user` - -- To convert Mermaid charts to SVG, PNG, and PDF formats. In addition to the Node.js version of Mermaid-CLI, you now have the option to use Python version Playwright, pyppeteer or mermaid.ink for this task. - - - Playwright - - **Install Playwright** - - ```bash - pip install playwright - ``` - - - **Install the Required Browsers** - - to support PDF conversion, please install Chrominum. - - ```bash - playwright install --with-deps chromium - ``` - - - **modify `config.yaml`** - - uncomment MERMAID_ENGINE from config.yaml and change it to `playwright` - - ```yaml - MERMAID_ENGINE: playwright - ``` - - - pyppeteer - - **Install pyppeteer** - - ```bash - pip install pyppeteer - ``` - - - **Use your own Browsers** - - pyppeteer allows you use installed browsers, please set the following envirment - - ```bash - export PUPPETEER_EXECUTABLE_PATH = /path/to/your/chromium or edge or chrome - ``` - - please do not use this command to install browser, it is too old - - ```bash - pyppeteer-install - ``` - - - **modify `config.yaml`** - - uncomment MERMAID_ENGINE from config.yaml and change it to `pyppeteer` - - ```yaml - MERMAID_ENGINE: pyppeteer - ``` - - - mermaid.ink - - **modify `config.yaml`** - - uncomment MERMAID_ENGINE from config.yaml and change it to `ink` - - ```yaml - MERMAID_ENGINE: ink - ``` - - Note: this method does not support pdf export. - -### Installation by Docker +### Docker installation ```bash # Step 1: Download metagpt official image and prepare config.yaml @@ -174,124 +76,46 @@ # Step 2: Run metagpt demo with container -v /opt/metagpt/workspace:/app/metagpt/workspace \ metagpt/metagpt:latest \ python startup.py "Write a cli snake game" - -# You can also start a container and execute commands in it -docker run --name metagpt -d \ - --privileged \ - -v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \ - -v /opt/metagpt/workspace:/app/metagpt/workspace \ - metagpt/metagpt:latest - -docker exec -it metagpt /bin/bash -$ python startup.py "Write a cli snake game" ``` -The command `docker run ...` do the following things: +detail installation please refer to [docker_install](docs/install/docker_install.md) -- Run in privileged mode to have permission to run the browser -- Map host configure file `/opt/metagpt/config/key.yaml` to container `/app/metagpt/config/key.yaml` -- Map host directory `/opt/metagpt/workspace` to container `/app/metagpt/workspace` -- Execute the demo command `python startup.py "Write a cli snake game"` +### QuickStart & Demo Video +- Try it on [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) +- [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) +- [Official Demo Video](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d) -### Build image by yourself +## Tutorial -```bash -# You can also build metagpt image by yourself. -git clone https://github.com/geekan/MetaGPT.git -cd MetaGPT && docker build -t metagpt:custom . -``` +- [Oneline Document]() +- [Usage](docs/tutorial/usage.md) +- [What can MetaGPT do?](docs/tutorial/what_can_this_do.md) +- How to build your own agents? + - [MetaGPT Usage & Development Guide](https://deepwisdom.feishu.cn/wiki/RUnswqUIPimRJmkkDZ7cLYwOndg#Yu2AdUvymoo67Jxbp0bcu8G4nEb) +- [Contribution](docs/develop/contribution.md) + - Develop RFC + - [Develop Roadmap](docs/ROADMAP.md) +- [Examples](docs/examples/README.md) + - Researcher + - Werewolf Game +- [FAQs](docs/tutorial/faq.md) +- [The generated projects display wall](https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419) -## Configuration +## Support -- Configure your `OPENAI_API_KEY` in any of `config/key.yaml / config/config.yaml / env` -- Priority order: `config/key.yaml > config/config.yaml > env` +### Discard Join US +📢 Join Our [Discord Channel](https://discord.gg/ZRHeExS6xv)! -```bash -# Copy the configuration file and make the necessary modifications. -cp config/config.yaml config/key.yaml -``` +Looking forward to seeing you there! 🎉 -| Variable Name | config/key.yaml | env | -| ------------------------------------------ | ----------------------------------------- | ----------------------------------------------- | -| OPENAI_API_KEY # Replace with your own key | OPENAI_API_KEY: "sk-..." | export OPENAI_API_KEY="sk-..." | -| OPENAI_API_BASE # Optional | OPENAI_API_BASE: "https:///v1" | export OPENAI_API_BASE="https:///v1" | +### Contact Information -## Tutorial: Initiating a startup +If you have any questions or feedback about this project, please feel free to contact us. We highly appreciate your suggestions! -```shell -# Run the script -python startup.py "Write a cli snake game" -# Do not hire an engineer to implement the project -python startup.py "Write a cli snake game" --implement False -# Hire an engineer and perform code reviews -python startup.py "Write a cli snake game" --code_review True -``` +- **Email:** alexanderwu@fuzhi.ai +- **GitHub Issues:** For more technical inquiries, you can also create a new issue in our [GitHub repository](https://github.com/geekan/metagpt/issues). -After running the script, you can find your new project in the `workspace/` directory. - -### Preference of Platform or Tool - -You can tell which platform or tool you want to use when stating your requirements. - -```shell -python startup.py "Write a cli snake game based on pygame" -``` - -### Usage - -``` -NAME - startup.py - We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. - -SYNOPSIS - startup.py IDEA - -DESCRIPTION - We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. - -POSITIONAL ARGUMENTS - IDEA - Type: str - Your innovative idea, such as "Creating a snake game." - -FLAGS - --investment=INVESTMENT - Type: float - Default: 3.0 - As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. - --n_round=N_ROUND - Type: int - Default: 5 - -NOTES - You can also use flags syntax for POSITIONAL ARGUMENTS -``` - -### Code walkthrough - -```python -from metagpt.software_company import SoftwareCompany -from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer - -async def startup(idea: str, investment: float = 3.0, n_round: int = 5): - """Run a startup. Be a boss.""" - company = SoftwareCompany() - company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) - company.invest(investment) - company.start_project(idea) - await company.run(n_round=n_round) -``` - -You can check `examples` for more details on single role (with knowledge base) and LLM only examples. - -## QuickStart - -It is difficult to install and configure the local environment for some users. The following tutorials will allow you to quickly experience the charm of MetaGPT. - -- [MetaGPT quickstart](https://deepwisdom.feishu.cn/wiki/CyY9wdJc4iNqArku3Lncl4v8n2b) - -Try it on Huggingface Space -- https://huggingface.co/spaces/deepwisdom/MetaGPT +We will respond to all questions within 2-3 business days. ## Citation @@ -307,23 +131,3 @@ ## Citation primaryClass={cs.AI} } ``` - -## Contact Information - -If you have any questions or feedback about this project, please feel free to contact us. We highly appreciate your suggestions! - -- **Email:** alexanderwu@fuzhi.ai -- **GitHub Issues:** For more technical inquiries, you can also create a new issue in our [GitHub repository](https://github.com/geekan/metagpt/issues). - -We will respond to all questions within 2-3 business days. - -## Demo - -https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d - -## Join us - -📢 Join Our Discord Channel! -https://discord.gg/ZRHeExS6xv - -Looking forward to seeing you there! 🎉 diff --git a/docs/README_CN.md b/docs/README_CN.md index 9d6f34c11..8c04e5066 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -12,7 +12,7 @@ # MetaGPT: 多智能体框架 CN doc EN doc JA doc -Discord Follow +Discord Follow License: MIT roadmap Twitter Follow @@ -33,22 +33,8 @@ # MetaGPT: 多智能体框架

软件公司多角色示意图(正在逐步实现)

-## MetaGPT 的能力 - -https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419 - - -## 示例(均由 GPT-4 生成) - -例如,键入`python startup.py "写个类似今日头条的推荐系统"`并回车,你会获得一系列输出,其一是数据结构与API设计 - -![今日头条 Recsys 数据 & API 设计](resources/workspace/content_rec_sys/resources/data_api_design.png) - -这需要大约**0.2美元**(GPT-4 API的费用)来生成一个带有分析和设计的示例,大约2.0美元用于一个完整的项目 - ## 安装 - -### 传统安装 +### Conda安装 ```bash # 第 1 步:确保您的系统上安装了 NPM。并使用npm安装mermaid-js @@ -56,32 +42,22 @@ # 第 1 步:确保您的系统上安装了 NPM。并使用npm安装mermaid-js sudo npm install -g @mermaid-js/mermaid-cli # 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下命令进行检查: +# 可以使用conda来初始化新的python环境 +# conda create -n metagpt python=3.9 +# conda activate metagpt python --version # 第 3 步:克隆仓库到您的本地机器,并进行安装。 git clone https://github.com/geekan/metagpt cd metagpt pip install -e. + +# 第 4 步:执行startup.py +# 拷贝config.yaml为key.yaml,并设置你自己的OPENAI_API_KEY +python3 startup.py "Write a cli snake game" ``` -**注意:** - -- 如果已经安装了Chrome、Chromium或MS Edge,可以通过将环境变量`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`设置为`true`来跳过下载Chromium。 - -- 一些人在全局安装此工具时遇到问题。在本地安装是替代解决方案, - - ```bash - npm install @mermaid-js/mermaid-cli - ``` - -- 不要忘记在config.yml中为mmdc配置配置, - - ```yml - PUPPETEER_CONFIG: "./config/puppeteer-config.json" - MMDC: "./node_modules/.bin/mmdc" - ``` - -- 如果`pip install -e.`失败并显示错误`[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`,请尝试使用`pip install -e. --user`运行。 +详细的安装请安装 [cli_install](docs/install/cli_install_cn.md) ### Docker安装 @@ -99,121 +75,39 @@ # 步骤2: 使用容器运行metagpt演示 -v /opt/metagpt/workspace:/app/metagpt/workspace \ metagpt/metagpt:latest \ python startup.py "Write a cli snake game" - -# 您也可以启动一个容器并在其中执行命令 -docker run --name metagpt -d \ - --privileged \ - -v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \ - -v /opt/metagpt/workspace:/app/metagpt/workspace \ - metagpt/metagpt:latest - -docker exec -it metagpt /bin/bash -$ python startup.py "Write a cli snake game" ``` -`docker run ...`做了以下事情: +详细的安装请安装 [docker_install](docs/install/docker_install_cn.md) -- 以特权模式运行,有权限运行浏览器 -- 将主机文件 `/opt/metagpt/config/key.yaml` 映射到容器文件 `/app/metagpt/config/key.yaml` -- 将主机目录 `/opt/metagpt/workspace` 映射到容器目录 `/app/metagpt/workspace` -- 执行示例命令 `python startup.py "Write a cli snake game"` +### 快速开始的演示视频 +- 在 [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) 上进行体验 +- [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) +- [官方演示视频](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d) -### 自己构建镜像 +## 教程 +- [在线文档]() +- [如何使用](docs/tutorial/usage_cn.md) +- [MetaGPT的能力及应用场景](docs/tutorial/what_can_this_do.md) +- 如何构建你自己得智能体? + - [MetaGPT的使用和开发教程](https://deepwisdom.feishu.cn/wiki/RUnswqUIPimRJmkkDZ7cLYwOndg#Yu2AdUvymoo67Jxbp0bcu8G4nEb) +- [贡献](docs/develop/contribution.md) + - 开发者RFC + - [开发路线图](docs/ROADMAP.md) +- [样例](docs/examples/README.md) + - 调研员 + - 狼人杀游戏 +- [常见问题解答](docs/tutorial/faq.md) +- [已生成项目的展示墙](https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419) -```bash -# 您也可以自己构建metagpt镜像 -git clone https://github.com/geekan/MetaGPT.git -cd MetaGPT && docker build -t metagpt:custom . -``` +## 支持 -## 配置 +### 加入我们 -- 在 `config/key.yaml / config/config.yaml / env` 中配置您的 `OPENAI_API_KEY` -- 优先级顺序:`config/key.yaml > config/config.yaml > env` +📢 加入我们的[Discord频道](https://discord.gg/ZRHeExS6xv)! -```bash -# 复制配置文件并进行必要的修改 -cp config/config.yaml config/key.yaml -``` +期待在那里与您相见!🎉 -| 变量名 | config/key.yaml | env | -| ----------------------------------- | ----------------------------------------- | ----------------------------------------------- | -| OPENAI_API_KEY # 用您自己的密钥替换 | OPENAI_API_KEY: "sk-..." | export OPENAI_API_KEY="sk-..." | -| OPENAI_API_BASE # 可选 | OPENAI_API_BASE: "https:///v1" | export OPENAI_API_BASE="https:///v1" | - -## 示例:启动一个创业公司 - -```shell -python startup.py "写一个命令行贪吃蛇" -# 开启code review模式会花费更多的金钱, 但是会提升代码质量和成功率 -python startup.py "写一个命令行贪吃蛇" --code_review True -``` - -运行脚本后,您可以在 `workspace/` 目录中找到您的新项目。 -### 平台或工具的倾向性 -可以在阐述需求时说明想要使用的平台或工具。 -例如: -```shell -python startup.py "写一个基于pygame的命令行贪吃蛇" -``` - -### 使用 - -``` -名称 - startup.py - 我们是一家AI软件创业公司。通过投资我们,您将赋能一个充满无限可能的未来。 - -概要 - startup.py IDEA - -描述 - 我们是一家AI软件创业公司。通过投资我们,您将赋能一个充满无限可能的未来。 - -位置参数 - IDEA - 类型: str - 您的创新想法,例如"写一个命令行贪吃蛇。" - -标志 - --investment=INVESTMENT - 类型: float - 默认值: 3.0 - 作为投资者,您有机会向这家AI公司投入一定的美元金额。 - --n_round=N_ROUND - 类型: int - 默认值: 5 - -备注 - 您也可以用`标志`的语法,来处理`位置参数` -``` - -### 代码实现 - -```python -from metagpt.software_company import SoftwareCompany -from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer - -async def startup(idea: str, investment: float = 3.0, n_round: int = 5): - """运行一个创业公司。做一个老板""" - company = SoftwareCompany() - company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) - company.invest(investment) - company.start_project(idea) - await company.run(n_round=n_round) -``` - -你可以查看`examples`,其中有单角色(带知识库)的使用例子与仅LLM的使用例子。 - -## 快速体验 -对一些用户来说,安装配置本地环境是有困难的,下面这些教程能够让你快速体验到MetaGPT的魅力。 - -- [MetaGPT快速体验](https://deepwisdom.feishu.cn/wiki/Q8ycw6J9tiNXdHk66MRcIN8Pnlg) - -可直接在Huggingface Space体验 - -- https://huggingface.co/spaces/deepwisdom/MetaGPT - -## 联系信息 +### 联系信息 如果您对这个项目有任何问题或反馈,欢迎联系我们。我们非常欢迎您的建议! @@ -222,13 +116,17 @@ ## 联系信息 我们会在2-3个工作日内回复所有问题。 -## 演示 +## 引用 -https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d +引用 [Arxiv paper](https://arxiv.org/abs/2308.00352): -## 加入我们 - -📢 加入我们的Discord频道! -https://discord.gg/ZRHeExS6xv - -期待在那里与您相见!🎉 +```bibtex +@misc{hong2023metagpt, + title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework}, + author={Sirui Hong and Xiawu Zheng and Jonathan Chen and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu}, + year={2023}, + eprint={2308.00352}, + archivePrefix={arXiv}, + primaryClass={cs.AI} +} +``` diff --git a/docs/examples/README.md b/docs/examples/README.md new file mode 100644 index 000000000..e69de29bb From 7d28509edccbd28db957c803447a19e4500262f9 Mon Sep 17 00:00:00 2001 From: better629 Date: Thu, 2 Nov 2023 21:27:52 +0800 Subject: [PATCH 04/53] update examples README --- docs/examples/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/examples/README.md b/docs/examples/README.md index e69de29bb..4fe4628e2 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -0,0 +1,12 @@ +## Examples +We introduce some example agents created recently and show how to created these agents under the framework. + +- Researcher + - With the given research topic, the agent will derive the keywords from the topic and then search the related documents using search engine api. + - The search result will be ranked and filtered to get high quality candidates. + - Summary the final content using the candidate documents as context. +- Invoice OCR and QA + - With the given one or multi invoices, the agent can recognize the text from the image or pdf. + - Organize the text with particular structure, generally, it will be saved in a csv. + - It can answer your question like `what's the total reimbursement of Alice?` +- Werewolf Game From 250fe83de9deecebecdec1d90b873cfeddeff3fb Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Mon, 6 Nov 2023 14:37:29 +0800 Subject: [PATCH 05/53] redefine react and provide multiple react modes --- examples/build_customized_agent.py | 38 ++-------- metagpt/roles/invoice_ocr_assistant.py | 26 +------ metagpt/roles/researcher.py | 19 +---- metagpt/roles/role.py | 98 ++++++++++++++++++++++---- 4 files changed, 94 insertions(+), 87 deletions(-) diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py index 87d7a9c76..2bc9e31e5 100644 --- a/examples/build_customized_agent.py +++ b/examples/build_customized_agent.py @@ -75,10 +75,9 @@ class SimpleCoder(Role): logger.info(f"{self._setting}: ready to {self._rc.todo}") todo = self._rc.todo - msg = self._rc.memory.get()[-1] # retrieve the latest memory - instruction = msg.content + msg = self.get_memories(k=1)[0] # find the most recent messages - code_text = await SimpleWriteCode().run(instruction) + code_text = await SimpleWriteCode().run(msg.content) msg = Message(content=code_text, role=self.profile, cause_by=todo) return msg @@ -92,43 +91,20 @@ class RunnableCoder(Role): ): super().__init__(name, profile, **kwargs) self._init_actions([SimpleWriteCode, SimpleRunCode]) - - async def _think(self) -> None: - if self._rc.todo is None: - self._set_state(0) - return - - if self._rc.state + 1 < len(self._states): - self._set_state(self._rc.state + 1) - else: - self._rc.todo = None + self._set_react_mode(react_mode="by_order") async def _act(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") todo = self._rc.todo - msg = self._rc.memory.get()[-1] - if isinstance(todo, SimpleWriteCode): - instruction = msg.content - result = await SimpleWriteCode().run(instruction) + msg = self.get_memories(k=1)[0] # find the most k recent messages + result = await todo.run(msg.content) - elif isinstance(todo, SimpleRunCode): - code_text = msg.content - result = await SimpleRunCode().run(code_text) - - msg = Message(content=result, role=self.profile, cause_by=todo) + msg = Message(content=result, role=self.profile, cause_by=type(todo)) self._rc.memory.add(msg) return msg - async def _react(self) -> Message: - while True: - await self._think() - if self._rc.todo is None: - break - await self._act() - return Message(content="All job done", role=self.profile) - -def main(msg="write a function that calculates the sum of a list"): +def main(msg="write a function that calculates the product of a list"): # role = SimpleCoder() role = RunnableCoder() logger.info(msg) diff --git a/metagpt/roles/invoice_ocr_assistant.py b/metagpt/roles/invoice_ocr_assistant.py index c307b20c0..15f831c97 100644 --- a/metagpt/roles/invoice_ocr_assistant.py +++ b/metagpt/roles/invoice_ocr_assistant.py @@ -42,17 +42,7 @@ class InvoiceOCRAssistant(Role): self.filename = "" self.origin_query = "" self.orc_data = None - - async def _think(self) -> None: - """Determine the next action to be taken by the role.""" - if self._rc.todo is None: - self._set_state(0) - return - - if self._rc.state + 1 < len(self._states): - self._set_state(self._rc.state + 1) - else: - self._rc.todo = None + self._set_react_mode(react_mode="by_order") async def _act(self) -> Message: """Perform an action as determined by the role. @@ -94,17 +84,3 @@ class InvoiceOCRAssistant(Role): msg = Message(content=content, instruct_content=resp) self._rc.memory.add(msg) return msg - - async def _react(self) -> Message: - """Execute the invoice ocr assistant's think and actions. - - Returns: - A message containing the final result of the assistant's actions. - """ - while True: - await self._think() - if self._rc.todo is None: - break - msg = await self._act() - return msg - diff --git a/metagpt/roles/researcher.py b/metagpt/roles/researcher.py index acb46c718..c5512121a 100644 --- a/metagpt/roles/researcher.py +++ b/metagpt/roles/researcher.py @@ -31,20 +31,11 @@ class Researcher(Role): ): super().__init__(name, profile, goal, constraints, **kwargs) self._init_actions([CollectLinks(name), WebBrowseAndSummarize(name), ConductResearch(name)]) + self._set_react_mode(react_mode="by_order") self.language = language if language not in ("en-us", "zh-cn"): logger.warning(f"The language `{language}` has not been tested, it may not work.") - async def _think(self) -> None: - if self._rc.todo is None: - self._set_state(0) - return - - if self._rc.state + 1 < len(self._states): - self._set_state(self._rc.state + 1) - else: - self._rc.todo = None - async def _act(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") todo = self._rc.todo @@ -73,12 +64,8 @@ class Researcher(Role): self._rc.memory.add(ret) return ret - async def _react(self) -> Message: - while True: - await self._think() - if self._rc.todo is None: - break - msg = await self._act() + async def react(self) -> Message: + msg = await super().react() report = msg.instruct_content self.write_report(report.topic, report.content) return msg diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 44bb3e976..88b98f4b4 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -7,7 +7,7 @@ """ from __future__ import annotations -from typing import Iterable, Type +from typing import Iterable, Type, Union from pydantic import BaseModel, Field @@ -27,12 +27,15 @@ Please note that only the text between the first and second "===" is information {history} === -You can now choose one of the following stages to decide the stage you need to go in the next step: +Your previous stage: {previous_state} + +Now choose one of the following stages you need to go to in the next step: {states} Just answer a number between 0-{n_states}, choose the most suitable stage according to the understanding of the conversation. Please note that the answer only needs a number, no need to add any other text. -If there is no conversation record, choose 0. +If there is no conversation record or your previous stage is None, choose 0. +If you think you have completed your goal and don't need to go to any of the stages, return -1. Do not answer anything else, and do not add any other information in your answer. """ @@ -67,7 +70,7 @@ class RoleContext(BaseModel): env: 'Environment' = Field(default=None) memory: Memory = Field(default_factory=Memory) long_term_memory: LongTermMemory = Field(default_factory=LongTermMemory) - state: int = Field(default=0) + state: int = Field(default=None) todo: Action = Field(default=None) watch: set[Type[Action]] = Field(default_factory=set) news: list[Type[Message]] = Field(default=[]) @@ -100,6 +103,9 @@ class Role: self._actions = [] self._role_id = str(self._setting) self._rc = RoleContext() + # see `_set_react_mode` function for definitions of the following two attributes + self.react_mode = "react" + self.max_react_loop = 1 def _reset(self): self._states = [] @@ -116,17 +122,37 @@ class Role: self._actions.append(i) self._states.append(f"{idx}. {action}") + def _set_react_mode(self, react_mode: str, max_react_loop: int = 1): + """Set strategy of the Role reacting to observed Message. Variation lies in how + this Role elects action to perform during the _think stage, especially if it is capable of multiple Actions. + + Args: + react_mode (str): Mode for choosing action during the _think stage, can be one of + "by_order": switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ...; + "react": standard think-act loop in the ReAct paper, alternating thinking and acting to solve the task, i.e. _think -> _act -> _think -> _act -> ... + Use llm to select actions in _think dynamically; + "plan_and_act": first plan, then execute an action sequence, i.e. _think (of a plan) -> _act -> _act -> ... + Use llm to come up with the plan dynamically. + Defaults to "by_order". + max_react_loop (int): Maximum react cycles to execute, used to prevent the agent from reacting forever. + Take effect only when react_mode is react, in which we use llm to choose actions, including termination. + Defaults to 1, i.e. _think -> _act (-> return result and end) + """ + self.react_mode = react_mode + if react_mode == "react": + self.max_react_loop = max_react_loop + def _watch(self, actions: Iterable[Type[Action]]): """Listen to the corresponding behaviors""" self._rc.watch.update(actions) # check RoleContext after adding watch actions self._rc.check(self._role_id) - def _set_state(self, state): + def _set_state(self, state: Union[int, None]): """Update the current state.""" self._rc.state = state logger.debug(self._actions) - self._rc.todo = self._actions[self._rc.state] + self._rc.todo = self._actions[self._rc.state] if state is not None else None def set_env(self, env: 'Environment'): """Set the environment in which the role works. The role can talk to the environment and can also receive messages by observing.""" @@ -151,13 +177,19 @@ class Role: return prompt = self._get_prefix() prompt += STATE_TEMPLATE.format(history=self._rc.history, states="\n".join(self._states), - n_states=len(self._states) - 1) + n_states=len(self._states) - 1, previous_state=self._rc.state) + # print(prompt) next_state = await self._llm.aask(prompt) logger.debug(f"{prompt=}") - if not next_state.isdigit() or int(next_state) not in range(len(self._states)): + if not next_state.isdigit() or int(next_state) not in range(-1, len(self._states)): logger.warning(f'Invalid answer of state, {next_state=}') - next_state = "0" - self._set_state(int(next_state)) + next_state = None + else: + next_state = int(next_state) + if next_state == -1: + logger.info(f"End actions with {next_state=}") + next_state = None + self._set_state(next_state) async def _act(self) -> Message: # prompt = self.get_prefix() @@ -203,10 +235,42 @@ class Role: self._rc.env.publish_message(msg) async def _react(self) -> Message: - """Think first, then act""" - await self._think() - logger.debug(f"{self._setting}: {self._rc.state=}, will do {self._rc.todo}") - return await self._act() + """Think first, then act, until the Role _think it is time to stop and requires no more todo. + This is the standard think-act loop in the ReAct paper, which alternates thinking and acting in task solving, i.e. _think -> _act -> _think -> _act -> ... + Use llm to select actions in _think dynamically + """ + actions_taken = 0 + while actions_taken < self.max_react_loop: + # think + await self._think() + if self._rc.todo is None: + break + # act + logger.debug(f"{self._setting}: {self._rc.state=}, will do {self._rc.todo}") + rsp = await self._act() + actions_taken += 1 + return rsp # return output from the last action + + async def _act_by_order(self) -> Message: + """switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ...""" + for i in range(len(self._states)): + self._set_state(i) + rsp = await self._act() + return rsp # return output from the last action + + async def _plan_and_act(self) -> Message: + """first plan, then execute an action sequence, i.e. _think (of a plan) -> _act -> _act -> ... Use llm to come up with the plan dynamically.""" + # TODO: to be implemented + return Message("") + + async def react(self) -> Message: + """Entry to one of three strategies by which Role reacts to the observed Message""" + if self.react_mode == "react": + return await self._react() + elif self.react_mode == "by_order": + return await self._act_by_order() + elif self.react_mode == "plan_and_act": + return await self._plan_and_act() def recv(self, message: Message) -> None: """add message to history.""" @@ -223,6 +287,10 @@ class Role: return await self._react() + def get_memories(self, k=0) -> list[Message]: + """A wrapper to return the most recent k memories of this role, return all when k=0""" + return self._rc.memory.get(k=k) + async def run(self, message=None): """Observe, and think and act based on the results of the observation""" if message: @@ -237,7 +305,7 @@ class Role: logger.debug(f"{self._setting}: no news. waiting.") return - rsp = await self._react() + rsp = await self.react() # Publish the reply to the environment, waiting for the next subscriber to process self._publish_message(rsp) return rsp From c84f021467ccaf2385a4b1a95a837b9f63624ee8 Mon Sep 17 00:00:00 2001 From: Sirui Hong <34952977+stellaHSR@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:45:14 +0800 Subject: [PATCH 06/53] Update README.md update paper citation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 70460ceb4..34fc970df 100644 --- a/README.md +++ b/README.md @@ -299,8 +299,8 @@ ## Citation ```bibtex @misc{hong2023metagpt, - title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework}, - author={Sirui Hong and Xiawu Zheng and Jonathan Chen and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu}, + title={MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework}, + author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Ceyao Zhang and Jinlin Wang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and Jürgen Schmidhuber}, year={2023}, eprint={2308.00352}, archivePrefix={arXiv}, From 0a57bad7425cfeb5ce22159eba2f2da9d7c95e9b Mon Sep 17 00:00:00 2001 From: Sirui Hong <34952977+stellaHSR@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:46:59 +0800 Subject: [PATCH 07/53] Update README_JA.md update paper citation --- docs/README_JA.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README_JA.md b/docs/README_JA.md index 2b2c35a62..cfacff0b9 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -299,8 +299,8 @@ ## 引用 ```bibtex @misc{hong2023metagpt, - title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework}, - author={Sirui Hong and Xiawu Zheng and Jonathan Chen and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu}, + title={MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework}, + author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Ceyao Zhang and Jinlin Wang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and Jürgen Schmidhuber}, year={2023}, eprint={2308.00352}, archivePrefix={arXiv}, From c6350efd7f9df1409454d3c3bd56039885d0964a Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Wed, 8 Nov 2023 14:20:12 +0800 Subject: [PATCH 08/53] default state to -1, mv react_mode to rc, use enum --- examples/build_customized_agent.py | 2 +- metagpt/roles/role.py | 61 ++++++++++++++++++------------ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py index 2bc9e31e5..c7069b768 100644 --- a/examples/build_customized_agent.py +++ b/examples/build_customized_agent.py @@ -104,7 +104,7 @@ class RunnableCoder(Role): self._rc.memory.add(msg) return msg -def main(msg="write a function that calculates the product of a list"): +def main(msg="write a function that calculates the product of a list and run it"): # role = SimpleCoder() role = RunnableCoder() logger.info(msg) diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 88b98f4b4..0251176f7 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -8,6 +8,7 @@ from __future__ import annotations from typing import Iterable, Type, Union +from enum import Enum from pydantic import BaseModel, Field @@ -34,7 +35,6 @@ Now choose one of the following stages you need to go to in the next step: Just answer a number between 0-{n_states}, choose the most suitable stage according to the understanding of the conversation. Please note that the answer only needs a number, no need to add any other text. -If there is no conversation record or your previous stage is None, choose 0. If you think you have completed your goal and don't need to go to any of the stages, return -1. Do not answer anything else, and do not add any other information in your answer. """ @@ -49,6 +49,14 @@ ROLE_TEMPLATE = """Your response should be based on the previous conversation hi {name}: {result} """ +class RoleReactMode(str, Enum): + REACT = "react" + BY_ORDER = "by_order" + PLAN_AND_ACT = "plan_and_act" + + @classmethod + def values(cls): + return [item.value for item in cls] class RoleSetting(BaseModel): """Role Settings""" @@ -70,10 +78,12 @@ class RoleContext(BaseModel): env: 'Environment' = Field(default=None) memory: Memory = Field(default_factory=Memory) long_term_memory: LongTermMemory = Field(default_factory=LongTermMemory) - state: int = Field(default=None) + state: int = Field(default=-1) # -1 indicates initial or termination state where todo is None todo: Action = Field(default=None) watch: set[Type[Action]] = Field(default_factory=set) news: list[Type[Message]] = Field(default=[]) + react_mode: RoleReactMode = RoleReactMode.REACT # see `Role._set_react_mode` for definitions of the following two attributes + max_react_loop: int = 1 class Config: arbitrary_types_allowed = True @@ -103,9 +113,6 @@ class Role: self._actions = [] self._role_id = str(self._setting) self._rc = RoleContext() - # see `_set_react_mode` function for definitions of the following two attributes - self.react_mode = "react" - self.max_react_loop = 1 def _reset(self): self._states = [] @@ -127,20 +134,21 @@ class Role: this Role elects action to perform during the _think stage, especially if it is capable of multiple Actions. Args: - react_mode (str): Mode for choosing action during the _think stage, can be one of - "by_order": switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ...; - "react": standard think-act loop in the ReAct paper, alternating thinking and acting to solve the task, i.e. _think -> _act -> _think -> _act -> ... + react_mode (str): Mode for choosing action during the _think stage, can be one of: + "react": standard think-act loop in the ReAct paper, alternating thinking and acting to solve the task, i.e. _think -> _act -> _think -> _act -> ... Use llm to select actions in _think dynamically; + "by_order": switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ...; "plan_and_act": first plan, then execute an action sequence, i.e. _think (of a plan) -> _act -> _act -> ... Use llm to come up with the plan dynamically. - Defaults to "by_order". + Defaults to "react". max_react_loop (int): Maximum react cycles to execute, used to prevent the agent from reacting forever. Take effect only when react_mode is react, in which we use llm to choose actions, including termination. Defaults to 1, i.e. _think -> _act (-> return result and end) """ - self.react_mode = react_mode - if react_mode == "react": - self.max_react_loop = max_react_loop + assert react_mode in RoleReactMode.values(), f"react_mode must be one of {RoleReactMode.values()}" + self._rc.react_mode = react_mode + if react_mode == RoleReactMode.REACT: + self._rc.max_react_loop = max_react_loop def _watch(self, actions: Iterable[Type[Action]]): """Listen to the corresponding behaviors""" @@ -148,11 +156,11 @@ class Role: # check RoleContext after adding watch actions self._rc.check(self._role_id) - def _set_state(self, state: Union[int, None]): + def _set_state(self, state: int): """Update the current state.""" self._rc.state = state logger.debug(self._actions) - self._rc.todo = self._actions[self._rc.state] if state is not None else None + self._rc.todo = self._actions[self._rc.state] if state >= 0 else None def set_env(self, env: 'Environment'): """Set the environment in which the role works. The role can talk to the environment and can also receive messages by observing.""" @@ -181,14 +189,14 @@ class Role: # print(prompt) next_state = await self._llm.aask(prompt) logger.debug(f"{prompt=}") - if not next_state.isdigit() or int(next_state) not in range(-1, len(self._states)): - logger.warning(f'Invalid answer of state, {next_state=}') - next_state = None + if (not next_state.isdigit() and next_state != "-1") \ + or int(next_state) not in range(-1, len(self._states)): + logger.warning(f'Invalid answer of state, {next_state=}, will be set to -1') + next_state = -1 else: next_state = int(next_state) if next_state == -1: logger.info(f"End actions with {next_state=}") - next_state = None self._set_state(next_state) async def _act(self) -> Message: @@ -240,7 +248,8 @@ class Role: Use llm to select actions in _think dynamically """ actions_taken = 0 - while actions_taken < self.max_react_loop: + rsp = Message("No actions taken yet") # will be overwritten after Role _act + while actions_taken < self._rc.max_react_loop: # think await self._think() if self._rc.todo is None: @@ -265,12 +274,14 @@ class Role: async def react(self) -> Message: """Entry to one of three strategies by which Role reacts to the observed Message""" - if self.react_mode == "react": - return await self._react() - elif self.react_mode == "by_order": - return await self._act_by_order() - elif self.react_mode == "plan_and_act": - return await self._plan_and_act() + if self._rc.react_mode == RoleReactMode.REACT: + rsp = await self._react() + elif self._rc.react_mode == RoleReactMode.BY_ORDER: + rsp = await self._act_by_order() + elif self._rc.react_mode == RoleReactMode.PLAN_AND_ACT: + rsp = await self._plan_and_act() + self._set_state(state=-1) # current reaction is complete, reset state to -1 and todo back to None + return rsp def recv(self, message: Message) -> None: """add message to history.""" From 56c2a162acc4221c1966f41dcc4f2d54549b26a2 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Wed, 8 Nov 2023 19:42:30 +0800 Subject: [PATCH 09/53] change PROJECT_ROOT setting & SoftwareCompany -> Team --- README.md | 4 +- docs/README_CN.md | 4 +- docs/README_JA.md | 4 +- examples/debate.py | 99 +++++++----------------- metagpt/const.py | 8 +- metagpt/roles/engineer.py | 1 + metagpt/{software_company.py => team.py} | 10 +-- startup.py | 4 +- tests/metagpt/roles/test_ui.py | 4 +- tests/metagpt/test_software_company.py | 6 +- 10 files changed, 54 insertions(+), 90 deletions(-) rename metagpt/{software_company.py => team.py} (79%) diff --git a/README.md b/README.md index 70460ceb4..1171ab83c 100644 --- a/README.md +++ b/README.md @@ -270,12 +270,12 @@ ### Usage ### Code walkthrough ```python -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer async def startup(idea: str, investment: float = 3.0, n_round: int = 5): """Run a startup. Be a boss.""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) company.invest(investment) company.start_project(idea) diff --git a/docs/README_CN.md b/docs/README_CN.md index 9d6f34c11..3aa0cb05d 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -190,12 +190,12 @@ ### 使用 ### 代码实现 ```python -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer async def startup(idea: str, investment: float = 3.0, n_round: int = 5): """运行一个创业公司。做一个老板""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) company.invest(investment) company.start_project(idea) diff --git a/docs/README_JA.md b/docs/README_JA.md index 2b2c35a62..538208631 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -270,12 +270,12 @@ ### 使用方法 ### コードウォークスルー ```python -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProjectManager, ProductManager, Architect, Engineer async def startup(idea: str, investment: float = 3.0, n_round: int = 5): """スタートアップを実行する。ボスになる。""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), Architect(), ProjectManager(), Engineer()]) company.invest(investment) company.start_project(idea) diff --git a/examples/debate.py b/examples/debate.py index 05db28070..4db7567f0 100644 --- a/examples/debate.py +++ b/examples/debate.py @@ -7,14 +7,14 @@ import asyncio import platform import fire -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.actions import Action, BossRequirement from metagpt.roles import Role from metagpt.schema import Message from metagpt.logs import logger -class ShoutOut(Action): - """Action: Shout out loudly in a debate (quarrel)""" +class SpeakAloud(Action): + """Action: Speak out aloud in a debate (quarrel)""" PROMPT_TEMPLATE = """ ## BACKGROUND @@ -27,7 +27,7 @@ class ShoutOut(Action): craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue: """ - def __init__(self, name="ShoutOut", context=None, llm=None): + def __init__(self, name="SpeakAloud", context=None, llm=None): super().__init__(name, context, llm) async def run(self, context: str, name: str, opponent_name: str): @@ -39,96 +39,55 @@ class ShoutOut(Action): return rsp -class Trump(Role): +class Debator(Role): def __init__( self, - name: str = "Trump", - profile: str = "Republican", + name: str, + profile: str, + opponent_name: str, **kwargs, ): super().__init__(name, profile, **kwargs) - self._init_actions([ShoutOut]) - self._watch([ShoutOut]) - self.name = "Trump" - self.opponent_name = "Biden" + self._init_actions([SpeakAloud]) + self._watch([BossRequirement, SpeakAloud]) + self.name = name + self.opponent_name = opponent_name async def _observe(self) -> int: await super()._observe() # accept messages sent (from opponent) to self, disregard own messages from the last round - self._rc.news = [msg for msg in self._rc.news if msg.send_to == self.name] + self._rc.news = [msg for msg in self._rc.news if msg.send_to == self.name] return len(self._rc.news) async def _act(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") + todo = self._rc.todo # An instance of SpeakAloud - msg_history = self._rc.memory.get_by_actions([ShoutOut]) - context = [] - for m in msg_history: - context.append(str(m)) - context = "\n".join(context) + memories = self.get_memories() + context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories) + # print(context) - rsp = await ShoutOut().run(context=context, name=self.name, opponent_name=self.opponent_name) + rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name) msg = Message( content=rsp, role=self.profile, - cause_by=ShoutOut, + cause_by=type(todo), sent_from=self.name, send_to=self.opponent_name, ) return msg -class Biden(Role): - def __init__( - self, - name: str = "Biden", - profile: str = "Democrat", - **kwargs, - ): - super().__init__(name, profile, **kwargs) - self._init_actions([ShoutOut]) - self._watch([BossRequirement, ShoutOut]) - self.name = "Biden" - self.opponent_name = "Trump" - - async def _observe(self) -> int: - await super()._observe() - # accept the very first human instruction (the debate topic) or messages sent (from opponent) to self, - # disregard own messages from the last round - self._rc.news = [msg for msg in self._rc.news if msg.cause_by == BossRequirement or msg.send_to == self.name] - return len(self._rc.news) - - async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") - - msg_history = self._rc.memory.get_by_actions([BossRequirement, ShoutOut]) - context = [] - for m in msg_history: - context.append(str(m)) - context = "\n".join(context) - - rsp = await ShoutOut().run(context=context, name=self.name, opponent_name=self.opponent_name) - - msg = Message( - content=rsp, - role=self.profile, - cause_by=ShoutOut, - sent_from=self.name, - send_to=self.opponent_name, - ) - - return msg - -async def startup(idea: str, investment: float = 3.0, n_round: int = 5, - code_review: bool = False, run_tests: bool = False): - """We reuse the startup paradigm for roles to interact with each other. - Now we run a startup of presidents and watch they quarrel. :) """ - company = SoftwareCompany() - company.hire([Biden(), Trump()]) - company.invest(investment) - company.start_project(idea) - await company.run(n_round=n_round) +async def debate(idea: str, investment: float = 3.0, n_round: int = 5): + """Run a team of presidents and watch they quarrel. :) """ + Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump") + Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden") + team = Team() + team.hire([Biden, Trump]) + team.invest(investment) + team.start_project(idea, send_to="Biden") # send debate topic to Biden and let him speak first + await team.run(n_round=n_round) def main(idea: str, investment: float = 3.0, n_round: int = 10): @@ -141,7 +100,7 @@ def main(idea: str, investment: float = 3.0, n_round: int = 10): """ if platform.system() == "Windows": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - asyncio.run(startup(idea, investment, n_round)) + asyncio.run(debate(idea, investment, n_round)) if __name__ == '__main__': diff --git a/metagpt/const.py b/metagpt/const.py index 7f3f87dfa..86b2b6c87 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -6,7 +6,7 @@ @File : const.py """ from pathlib import Path - +from loguru import logger def get_project_root(): """Search upwards to find the project root directory.""" @@ -17,10 +17,14 @@ def get_project_root(): or (current_path / ".project_root").exists() or (current_path / ".gitignore").exists() ): + # use metagpt with git clone will land here return current_path parent_path = current_path.parent if parent_path == current_path: - raise Exception("Project root not found.") + # use metagpt with pip install will land here + cwd = Path.cwd() + logger.info(f"RPOJECT_ROOT set to current working directory: {str(cwd)}") + return cwd current_path = parent_path diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 6d65575a8..1f6685b38 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -207,6 +207,7 @@ class Engineer(Role): async def _act(self) -> Message: """Determines the mode of action based on whether code review is used.""" + logger.info(f"{self._setting}: ready to WriteCode") if self.use_code_review: return await self._act_sp_precision() return await self._act_sp() diff --git a/metagpt/software_company.py b/metagpt/team.py similarity index 79% rename from metagpt/software_company.py rename to metagpt/team.py index b2bd18c58..67d3ecec8 100644 --- a/metagpt/software_company.py +++ b/metagpt/team.py @@ -16,10 +16,10 @@ from metagpt.schema import Message from metagpt.utils.common import NoMoneyException -class SoftwareCompany(BaseModel): +class Team(BaseModel): """ - Software Company: Possesses a team, SOP (Standard Operating Procedures), and a platform for instant messaging, - dedicated to writing executable code. + Team: Possesses one or more roles (agents), SOP (Standard Operating Procedures), and a platform for instant messaging, + dedicated to perform any multi-agent activity, such as collaboratively writing executable code. """ environment: Environment = Field(default_factory=Environment) investment: float = Field(default=10.0) @@ -42,10 +42,10 @@ class SoftwareCompany(BaseModel): if CONFIG.total_cost > CONFIG.max_budget: raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}') - def start_project(self, idea): + def start_project(self, idea, send_to: str = ""): """Start a project from publishing boss requirement.""" self.idea = idea - self.environment.publish_message(Message(role="BOSS", content=idea, cause_by=BossRequirement)) + self.environment.publish_message(Message(role="Human", content=idea, cause_by=BossRequirement, send_to=send_to)) def _save(self): logger.info(self.json()) diff --git a/startup.py b/startup.py index e2a903c9b..e9fbf94d3 100644 --- a/startup.py +++ b/startup.py @@ -11,7 +11,7 @@ from metagpt.roles import ( ProjectManager, QaEngineer, ) -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team async def startup( @@ -23,7 +23,7 @@ async def startup( implement: bool = True, ): """Run a startup. Be a boss.""" - company = SoftwareCompany() + company = Team() company.hire( [ ProductManager(), diff --git a/tests/metagpt/roles/test_ui.py b/tests/metagpt/roles/test_ui.py index 285bff323..d58d31bd9 100644 --- a/tests/metagpt/roles/test_ui.py +++ b/tests/metagpt/roles/test_ui.py @@ -2,7 +2,7 @@ # @Date : 2023/7/22 02:40 # @Author : stellahong (stellahong@fuzhi.ai) # -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team from metagpt.roles import ProductManager from tests.metagpt.roles.ui_role import UI @@ -15,7 +15,7 @@ def test_add_ui(): async def test_ui_role(idea: str, investment: float = 3.0, n_round: int = 5): """Run a startup. Be a boss.""" - company = SoftwareCompany() + company = Team() company.hire([ProductManager(), UI()]) company.invest(investment) company.start_project(idea) diff --git a/tests/metagpt/test_software_company.py b/tests/metagpt/test_software_company.py index 00538442c..4fc651f52 100644 --- a/tests/metagpt/test_software_company.py +++ b/tests/metagpt/test_software_company.py @@ -8,12 +8,12 @@ import pytest from metagpt.logs import logger -from metagpt.software_company import SoftwareCompany +from metagpt.team import Team @pytest.mark.asyncio -async def test_software_company(): - company = SoftwareCompany() +async def test_team(): + company = Team() company.start_project("做一个基础搜索引擎,可以支持知识库") history = await company.run(n_round=5) logger.info(history) From 0bf2ce707e6fdf65a883da9dbf82d9bb2682f337 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Thu, 9 Nov 2023 01:41:24 +0800 Subject: [PATCH 10/53] allow human to play any roles --- metagpt/llm.py | 1 + metagpt/provider/human_gpt.py | 35 +++++++++++++++++++++++++++++++++++ metagpt/roles/role.py | 15 ++++++++++----- 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 metagpt/provider/human_gpt.py diff --git a/metagpt/llm.py b/metagpt/llm.py index e6f815950..8b4a13838 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -8,6 +8,7 @@ from metagpt.provider.anthropic_api import Claude2 as Claude from metagpt.provider.openai_api import OpenAIGPTAPI as LLM +from metagpt.provider.human_gpt import HumanGPT DEFAULT_LLM = LLM() CLAUDE_LLM = Claude() diff --git a/metagpt/provider/human_gpt.py b/metagpt/provider/human_gpt.py new file mode 100644 index 000000000..bc4d6dc6e --- /dev/null +++ b/metagpt/provider/human_gpt.py @@ -0,0 +1,35 @@ +''' +Filename: MetaGPT/metagpt/provider/human_speaker.py +Created Date: Wednesday, November 8th 2023, 11:55:46 pm +Author: garylin2099 +''' +from typing import Optional +from metagpt.provider.base_gpt_api import BaseGPTAPI +from metagpt.logs import logger + +class HumanGPT(BaseGPTAPI): + """A dummy GPT that actually takes in human input as its response. + This enables replacing LLM anywhere in the framework with a human, thus introducing human interaction + """ + + def ask(self, msg: str) -> str: + logger.info("It's your turn, please type in your response. You may also refer to the context below") + rsp = input(msg) + if rsp in ["exit", "quit"]: + exit() + return rsp + + async def aask(self, msg: str, system_msgs: Optional[list[str]] = None) -> str: + return self.ask(msg) + + def completion(self, messages: list[dict]): + """dummy implementation of abstract method in base""" + return [] + + async def acompletion(self, messages: list[dict]): + """dummy implementation of abstract method in base""" + return [] + + async def acompletion_text(self, messages: list[dict], stream=False) -> str: + """dummy implementation of abstract method in base""" + return [] diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 0251176f7..fecca0beb 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, Field # from metagpt.environment import Environment from metagpt.config import CONFIG from metagpt.actions import Action, ActionOutput -from metagpt.llm import LLM +from metagpt.llm import LLM, HumanGPT from metagpt.logs import logger from metagpt.memory import Memory, LongTermMemory from metagpt.schema import Message @@ -65,6 +65,7 @@ class RoleSetting(BaseModel): goal: str constraints: str desc: str + is_human: bool def __str__(self): return f"{self.name}({self.profile})" @@ -106,9 +107,10 @@ class RoleContext(BaseModel): class Role: """Role/Agent""" - def __init__(self, name="", profile="", goal="", constraints="", desc=""): - self._llm = LLM() - self._setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc) + def __init__(self, name="", profile="", goal="", constraints="", desc="", is_human=False): + self._llm = LLM() if not is_human else HumanGPT() + self._setting = RoleSetting(name=name, profile=profile, goal=goal, + constraints=constraints, desc=desc, is_human=is_human) self._states = [] self._actions = [] self._role_id = str(self._setting) @@ -122,8 +124,11 @@ class Role: self._reset() for idx, action in enumerate(actions): if not isinstance(action, Action): - i = action("") + i = action("", llm=self._llm) else: + if self._setting.is_human and not isinstance(action.llm, HumanGPT): + logger.warning(f"is_human attribute does not take effect," + f"as Role's {str(action)} was initialized using LLM, try passing in Action classes instead of initialized instances") i = action i.set_prefix(self._get_prefix(), self.profile) self._actions.append(i) From a5bc55f18a6cab172e10ad77270ae9ed1d1f7e5a Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Thu, 9 Nov 2023 16:41:24 +0800 Subject: [PATCH 11/53] add deprecated warnings --- metagpt/software_company.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 metagpt/software_company.py diff --git a/metagpt/software_company.py b/metagpt/software_company.py new file mode 100644 index 000000000..d44a0068a --- /dev/null +++ b/metagpt/software_company.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/12 00:30 +@Author : alexanderwu +@File : software_company.py +""" +from metagpt.team import Team as SoftwareCompany + +import warnings +warnings.warn("metagpt.software_company is deprecated and will be removed in the future" + "Please use metagpt.team instead. SoftwareCompany class is now named as Team.", + DeprecationWarning, 2) From f0098f0ae9fef395b3842f15c8410eb29e6a916b Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Thu, 9 Nov 2023 16:51:20 +0800 Subject: [PATCH 12/53] add PROJECT_ROOT logging info --- metagpt/const.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metagpt/const.py b/metagpt/const.py index 86b2b6c87..407ce803a 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -18,12 +18,13 @@ def get_project_root(): or (current_path / ".gitignore").exists() ): # use metagpt with git clone will land here + logger.info(f"PROJECT_ROOT set to {str(current_path)}") return current_path parent_path = current_path.parent if parent_path == current_path: # use metagpt with pip install will land here cwd = Path.cwd() - logger.info(f"RPOJECT_ROOT set to current working directory: {str(cwd)}") + logger.info(f"PROJECT_ROOT set to current working directory: {str(cwd)}") return cwd current_path = parent_path From 771a750e344bfd3e4f18388195a66d3aabae4a8f Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Fri, 10 Nov 2023 14:56:31 +0800 Subject: [PATCH 13/53] release 0.3.0 --- metagpt/utils/mermaid.py | 5 ++++- setup.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/metagpt/utils/mermaid.py b/metagpt/utils/mermaid.py index 5e5b275b0..204c22c67 100644 --- a/metagpt/utils/mermaid.py +++ b/metagpt/utils/mermaid.py @@ -34,7 +34,10 @@ async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, engine = CONFIG.mermaid_engine.lower() if engine == "nodejs": if check_cmd_exists(CONFIG.mmdc) != 0: - logger.warning("RUN `npm install -g @mermaid-js/mermaid-cli` to install mmdc") + logger.warning( + "RUN `npm install -g @mermaid-js/mermaid-cli` to install mmdc," + "or consider changing MERMAID_ENGINE to `playwright`, `pyppeteer`, or `ink`." + ) return -1 for suffix in ["pdf", "svg", "png"]: diff --git a/setup.py b/setup.py index f9ae768e6..239156ae3 100644 --- a/setup.py +++ b/setup.py @@ -30,16 +30,16 @@ with open(path.join(here, "requirements.txt"), encoding="utf-8") as f: setup( name="metagpt", - version="0.1", + version="0.3.0", description="The Multi-Role Meta Programming Framework", long_description=long_description, long_description_content_type="text/markdown", - url="https://gitlab.deepwisdomai.com/pub/metagpt", + url="https://github.com/geekan/MetaGPT", author="Alexander Wu", author_email="alexanderwu@fuzhi.ai", license="Apache 2.0", keywords="metagpt multi-role multi-agent programming gpt llm", - packages=find_packages(exclude=["contrib", "docs", "examples"]), + packages=find_packages(exclude=["contrib", "docs", "examples", "tests*"]), python_requires=">=3.9", install_requires=requirements, extras_require={ From 7bf808ee258fe15ab08ad234a081ef1660548833 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Fri, 17 Nov 2023 00:20:46 +0800 Subject: [PATCH 14/53] renaming & provide example & small bug fix --- examples/build_customized_agent.py | 17 +- examples/build_customized_multi_agents.py | 150 ++++++++++++++++++ examples/debate.py | 2 + metagpt/llm.py | 2 +- .../{human_gpt.py => human_provider.py} | 6 +- metagpt/roles/role.py | 6 +- 6 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 examples/build_customized_multi_agents.py rename metagpt/provider/{human_gpt.py => human_provider.py} (85%) diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py index c7069b768..ed00e8320 100644 --- a/examples/build_customized_agent.py +++ b/examples/build_customized_agent.py @@ -19,15 +19,6 @@ class SimpleWriteCode(Action): PROMPT_TEMPLATE = """ Write a python function that can {instruction} and provide two runnnable test cases. Return ```python your_code_here ``` with NO other texts, - example: - ```python - # function - def add(a, b): - return a + b - # test cases - print(add(1, 2)) - print(add(3, 4)) - ``` your code: """ @@ -73,12 +64,12 @@ class SimpleCoder(Role): async def _act(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") - todo = self._rc.todo + todo = self._rc.todo # todo will be SimpleWriteCode() msg = self.get_memories(k=1)[0] # find the most recent messages - code_text = await SimpleWriteCode().run(msg.content) - msg = Message(content=code_text, role=self.profile, cause_by=todo) + code_text = await todo.run(msg.content) + msg = Message(content=code_text, role=self.profile, cause_by=type(todo)) return msg @@ -95,6 +86,8 @@ class RunnableCoder(Role): async def _act(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") + # By choosing the Action by order under the hood + # todo will be first SimpleWriteCode() then SimpleRunCode() todo = self._rc.todo msg = self.get_memories(k=1)[0] # find the most k recent messages diff --git a/examples/build_customized_multi_agents.py b/examples/build_customized_multi_agents.py new file mode 100644 index 000000000..02221e8e9 --- /dev/null +++ b/examples/build_customized_multi_agents.py @@ -0,0 +1,150 @@ +''' +Filename: MetaGPT/examples/build_customized_multi_agents.py +Created Date: Wednesday, November 15th 2023, 7:12:39 pm +Author: garylin2099 +''' +import re +import asyncio +import fire +from metagpt.actions import Action, BossRequirement +from metagpt.roles import Role +from metagpt.team import Team +from metagpt.schema import Message +from metagpt.logs import logger + +def parse_code(rsp): + pattern = r'```python(.*)```' + match = re.search(pattern, rsp, re.DOTALL) + code_text = match.group(1) if match else rsp + return code_text + +class SimpleWriteCode(Action): + + PROMPT_TEMPLATE = """ + Write a python function that can {instruction}. + Return ```python your_code_here ``` with NO other texts, + your code: + """ + + def __init__(self, name="SimpleWriteCode", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, instruction: str): + + prompt = self.PROMPT_TEMPLATE.format(instruction=instruction) + + rsp = await self._aask(prompt) + + code_text = parse_code(rsp) + + return code_text + +class SimpleCoder(Role): + def __init__( + self, + name: str = "Alice", + profile: str = "SimpleCoder", + **kwargs, + ): + super().__init__(name, profile, **kwargs) + self._watch([BossRequirement]) + self._init_actions([SimpleWriteCode]) + +class SimpleWriteTest(Action): + + PROMPT_TEMPLATE = """ + Context: {context} + Write {k} unit tests using pytest for the given function, assuming you have imported it. + Return ```python your_code_here ``` with NO other texts, + your code: + """ + + def __init__(self, name="SimpleWriteTest", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, context: str, k: int = 3): + + prompt = self.PROMPT_TEMPLATE.format(context=context, k=k) + + rsp = await self._aask(prompt) + + code_text = parse_code(rsp) + + return code_text + +class SimpleTester(Role): + def __init__( + self, + name: str = "Bob", + profile: str = "SimpleTester", + **kwargs, + ): + super().__init__(name, profile, **kwargs) + self._init_actions([SimpleWriteTest]) + # self._watch([SimpleWriteCode]) + self._watch([SimpleWriteCode, SimpleWriteReview]) # feel free to try this too + + async def _act(self) -> Message: + logger.info(f"{self._setting}: ready to {self._rc.todo}") + todo = self._rc.todo + + # context = self.get_memories(k=1)[0].content # use the most recent memory as context + context = self.get_memories() # use all memories as context + + code_text = await todo.run(context, k=5) # specify arguments + msg = Message(content=code_text, role=self.profile, cause_by=type(todo)) + + return msg + +class SimpleWriteReview(Action): + + PROMPT_TEMPLATE = """ + Context: {context} + Review the test cases and provide one critical comments: + """ + + def __init__(self, name="SimpleWriteReview", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, context: str): + + prompt = self.PROMPT_TEMPLATE.format(context=context) + + rsp = await self._aask(prompt) + + return rsp + +class SimpleReviewer(Role): + def __init__( + self, + name: str = "Charlie", + profile: str = "SimpleReviewer", + **kwargs, + ): + super().__init__(name, profile, **kwargs) + self._init_actions([SimpleWriteReview]) + self._watch([SimpleWriteTest]) + +async def main( + idea: str = "write a function that calculates the product of a list", + investment: float = 3.0, + n_round: int = 5, + add_human: bool = False, +): + logger.info(idea) + + team = Team() + team.hire( + [ + SimpleCoder(), + SimpleTester(), + SimpleReviewer(is_human=add_human), + ] + ) + + team.invest(investment=investment) + team.start_project(idea) + await team.run(n_round=n_round) + +if __name__ == '__main__': + fire.Fire(main) diff --git a/examples/debate.py b/examples/debate.py index 4db7567f0..a37e60848 100644 --- a/examples/debate.py +++ b/examples/debate.py @@ -77,6 +77,8 @@ class Debator(Role): send_to=self.opponent_name, ) + self._rc.memory.add(msg) + return msg async def debate(idea: str, investment: float = 3.0, n_round: int = 5): diff --git a/metagpt/llm.py b/metagpt/llm.py index 8b4a13838..9324da126 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -8,7 +8,7 @@ from metagpt.provider.anthropic_api import Claude2 as Claude from metagpt.provider.openai_api import OpenAIGPTAPI as LLM -from metagpt.provider.human_gpt import HumanGPT +from metagpt.provider.human_provider import HumanProvider DEFAULT_LLM = LLM() CLAUDE_LLM = Claude() diff --git a/metagpt/provider/human_gpt.py b/metagpt/provider/human_provider.py similarity index 85% rename from metagpt/provider/human_gpt.py rename to metagpt/provider/human_provider.py index bc4d6dc6e..1d12f972f 100644 --- a/metagpt/provider/human_gpt.py +++ b/metagpt/provider/human_provider.py @@ -1,5 +1,5 @@ ''' -Filename: MetaGPT/metagpt/provider/human_speaker.py +Filename: MetaGPT/metagpt/provider/human_provider.py Created Date: Wednesday, November 8th 2023, 11:55:46 pm Author: garylin2099 ''' @@ -7,8 +7,8 @@ from typing import Optional from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.logs import logger -class HumanGPT(BaseGPTAPI): - """A dummy GPT that actually takes in human input as its response. +class HumanProvider(BaseGPTAPI): + """Humans provide themselves as a 'model', which actually takes in human input as its response. This enables replacing LLM anywhere in the framework with a human, thus introducing human interaction """ diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index fecca0beb..b96c361c0 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -15,7 +15,7 @@ from pydantic import BaseModel, Field # from metagpt.environment import Environment from metagpt.config import CONFIG from metagpt.actions import Action, ActionOutput -from metagpt.llm import LLM, HumanGPT +from metagpt.llm import LLM, HumanProvider from metagpt.logs import logger from metagpt.memory import Memory, LongTermMemory from metagpt.schema import Message @@ -108,7 +108,7 @@ class Role: """Role/Agent""" def __init__(self, name="", profile="", goal="", constraints="", desc="", is_human=False): - self._llm = LLM() if not is_human else HumanGPT() + self._llm = LLM() if not is_human else HumanProvider() self._setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, is_human=is_human) self._states = [] @@ -126,7 +126,7 @@ class Role: if not isinstance(action, Action): i = action("", llm=self._llm) else: - if self._setting.is_human and not isinstance(action.llm, HumanGPT): + if self._setting.is_human and not isinstance(action.llm, HumanProvider): logger.warning(f"is_human attribute does not take effect," f"as Role's {str(action)} was initialized using LLM, try passing in Action classes instead of initialized instances") i = action From 5c66b29a80e538cb5db0e11b5844c335c55c3864 Mon Sep 17 00:00:00 2001 From: Sirui Hong <34952977+stellaHSR@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:56:29 +0800 Subject: [PATCH 15/53] Update README.md rm as waitlist --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d2ca26cdd..1307fbb53 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ # MetaGPT: The Multi-Agent Framework

- AgentStore Waitlist Open in Dev Containers Open in GitHub Codespaces Hugging Face From aa14c6664bddc91742de7a30dc44657eb4a517f8 Mon Sep 17 00:00:00 2001 From: Sirui Hong <34952977+stellaHSR@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:56:59 +0800 Subject: [PATCH 16/53] Update README_CN.md rm as waitlist --- docs/README_CN.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/README_CN.md b/docs/README_CN.md index 3aa0cb05d..a49ca9b77 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -19,7 +19,6 @@ # MetaGPT: 多智能体框架

- AgentStore Waitlist Open in Dev Containers Open in GitHub Codespaces Hugging Face From f8ebfa9a742abf3f0b187428a6c14d328c8c1460 Mon Sep 17 00:00:00 2001 From: Sirui Hong <34952977+stellaHSR@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:57:28 +0800 Subject: [PATCH 17/53] Update README_JA.md rm as waitlist --- docs/README_JA.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/README_JA.md b/docs/README_JA.md index 1ced60b83..cba84df80 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -19,7 +19,6 @@ # MetaGPT: マルチエージェントフレームワーク

- AgentStore Waitlist Open in Dev Containers Open in GitHub Codespaces Hugging Face From 705a3e962b2d5a7d8bb643901b84f0d9b34870e2 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Fri, 17 Nov 2023 14:40:07 +0800 Subject: [PATCH 18/53] formatting --- examples/build_customized_agent.py | 9 +++++++-- examples/build_customized_multi_agents.py | 14 +++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py index ed00e8320..be34e5e5e 100644 --- a/examples/build_customized_agent.py +++ b/examples/build_customized_agent.py @@ -9,6 +9,7 @@ import asyncio import fire +from metagpt.llm import LLM from metagpt.actions import Action from metagpt.roles import Role from metagpt.schema import Message @@ -22,7 +23,7 @@ class SimpleWriteCode(Action): your code: """ - def __init__(self, name="SimpleWriteCode", context=None, llm=None): + def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None): super().__init__(name, context, llm) async def run(self, instruction: str): @@ -42,8 +43,9 @@ class SimpleWriteCode(Action): code_text = match.group(1) if match else rsp return code_text + class SimpleRunCode(Action): - def __init__(self, name="SimpleRunCode", context=None, llm=None): + def __init__(self, name: str = "SimpleRunCode", context=None, llm: LLM = None): super().__init__(name, context, llm) async def run(self, code_text: str): @@ -52,6 +54,7 @@ class SimpleRunCode(Action): logger.info(f"{code_result=}") return code_result + class SimpleCoder(Role): def __init__( self, @@ -73,6 +76,7 @@ class SimpleCoder(Role): return msg + class RunnableCoder(Role): def __init__( self, @@ -97,6 +101,7 @@ class RunnableCoder(Role): self._rc.memory.add(msg) return msg + def main(msg="write a function that calculates the product of a list and run it"): # role = SimpleCoder() role = RunnableCoder() diff --git a/examples/build_customized_multi_agents.py b/examples/build_customized_multi_agents.py index 02221e8e9..0df927e32 100644 --- a/examples/build_customized_multi_agents.py +++ b/examples/build_customized_multi_agents.py @@ -6,6 +6,8 @@ Author: garylin2099 import re import asyncio import fire + +from metagpt.llm import LLM from metagpt.actions import Action, BossRequirement from metagpt.roles import Role from metagpt.team import Team @@ -26,7 +28,7 @@ class SimpleWriteCode(Action): your code: """ - def __init__(self, name="SimpleWriteCode", context=None, llm=None): + def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None): super().__init__(name, context, llm) async def run(self, instruction: str): @@ -39,6 +41,7 @@ class SimpleWriteCode(Action): return code_text + class SimpleCoder(Role): def __init__( self, @@ -50,6 +53,7 @@ class SimpleCoder(Role): self._watch([BossRequirement]) self._init_actions([SimpleWriteCode]) + class SimpleWriteTest(Action): PROMPT_TEMPLATE = """ @@ -59,7 +63,7 @@ class SimpleWriteTest(Action): your code: """ - def __init__(self, name="SimpleWriteTest", context=None, llm=None): + def __init__(self, name: str = "SimpleWriteTest", context=None, llm: LLM = None): super().__init__(name, context, llm) async def run(self, context: str, k: int = 3): @@ -72,6 +76,7 @@ class SimpleWriteTest(Action): return code_text + class SimpleTester(Role): def __init__( self, @@ -96,6 +101,7 @@ class SimpleTester(Role): return msg + class SimpleWriteReview(Action): PROMPT_TEMPLATE = """ @@ -103,7 +109,7 @@ class SimpleWriteReview(Action): Review the test cases and provide one critical comments: """ - def __init__(self, name="SimpleWriteReview", context=None, llm=None): + def __init__(self, name: str = "SimpleWriteReview", context=None, llm: LLM = None): super().__init__(name, context, llm) async def run(self, context: str): @@ -114,6 +120,7 @@ class SimpleWriteReview(Action): return rsp + class SimpleReviewer(Role): def __init__( self, @@ -125,6 +132,7 @@ class SimpleReviewer(Role): self._init_actions([SimpleWriteReview]) self._watch([SimpleWriteTest]) + async def main( idea: str = "write a function that calculates the product of a list", investment: float = 3.0, From 0153bb5416042bf449a2885b4f255627abf45402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Sat, 18 Nov 2023 16:09:56 +0800 Subject: [PATCH 19/53] feat: add ask code via function. --- metagpt/provider/base_gpt_api.py | 20 ++++++++-- metagpt/provider/openai_api.py | 55 +++++++++++++++++++++++++-- metagpt/utils/function_schema.py | 29 ++++++++++++++ tests/metagpt/provider/test_openai.py | 22 +++++++++++ 4 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 metagpt/utils/function_schema.py create mode 100644 tests/metagpt/provider/test_openai.py diff --git a/metagpt/provider/base_gpt_api.py b/metagpt/provider/base_gpt_api.py index de61167b9..2cfc3fa1f 100644 --- a/metagpt/provider/base_gpt_api.py +++ b/metagpt/provider/base_gpt_api.py @@ -5,6 +5,7 @@ @Author : alexanderwu @File : base_gpt_api.py """ +import json from abc import abstractmethod from typing import Optional @@ -14,7 +15,8 @@ from metagpt.provider.base_chatbot import BaseChatbot class BaseGPTAPI(BaseChatbot): """GPT API abstract class, requiring all inheritors to provide a series of standard capabilities""" - system_prompt = 'You are a helpful assistant.' + + system_prompt = "You are a helpful assistant." def _user_msg(self, msg: str) -> dict[str, str]: return {"role": "user", "content": msg} @@ -108,11 +110,23 @@ class BaseGPTAPI(BaseChatbot): """Required to provide the first text of choice""" return rsp.get("choices")[0]["message"]["content"] + def get_choice_function(self, rsp: dict) -> dict: + """Required to provide the first function of choice. for example: + "function": { + "name": "execute", + "arguments": "{\n \"language\": \"python\",\n \"code\": \"print('Hello, World!')\"\n}" + } + """ + return rsp.get("choices")[0]["message"]["tool_calls"][0]["function"].to_dict() + + def get_choice_function_arguments(self, rsp: dict) -> dict: + """Required to provide the first function arguments of choice.""" + return json.loads(self.get_choice_function(rsp)["arguments"]) + def messages_to_prompt(self, messages: list[dict]): """[{"role": "user", "content": msg}] to user: etc.""" - return '\n'.join([f"{i['role']}: {i['content']}" for i in messages]) + return "\n".join([f"{i['role']}: {i['content']}" for i in messages]) def messages_to_dict(self, messages): """objects to [{"role": "user", "content": msg}] etc.""" return [i.to_dict() for i in messages] - \ No newline at end of file diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 6ebed2c16..b9698e77d 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -21,6 +21,7 @@ from tenacity import ( from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI +from metagpt.utils.function_schema import general_function_schema, general_tool_choice from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( TOKEN_COSTS, @@ -110,7 +111,6 @@ class CostManager(metaclass=Singleton): """ return self.total_completion_tokens - def get_total_cost(self): """ Get the total cost of API calls. @@ -120,7 +120,6 @@ class CostManager(metaclass=Singleton): """ return self.total_cost - def get_costs(self) -> Costs: """Get all costs""" return Costs(self.total_prompt_tokens, self.total_completion_tokens, self.total_cost, self.total_budget) @@ -181,7 +180,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): self._update_costs(usage) return full_reply_content - def _cons_kwargs(self, messages: list[dict]) -> dict: + def _cons_kwargs(self, messages: list[dict], **configs) -> dict: kwargs = { "messages": messages, "max_tokens": self.get_max_tokens(messages), @@ -190,6 +189,9 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): "temperature": 0.3, "timeout": 3, } + if configs: + kwargs.update(configs) + if CONFIG.openai_api_type == "azure": if CONFIG.deployment_name and CONFIG.deployment_id: raise ValueError("You can only use one of the `deployment_id` or `deployment_name` model") @@ -239,6 +241,53 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): rsp = await self._achat_completion(messages) return self.get_choice_text(rsp) + def _func_configs(self, messages: list[dict], **kwargs) -> dict: + if "tools" not in kwargs: + configs = { + "tools": [{"type": "function", "function": general_function_schema}], + "tool_choice": general_tool_choice, + } + kwargs.update(configs) + + return self._cons_kwargs(messages, **kwargs) + + def _chat_completion_function(self, messages: list[dict], **kwargs) -> dict: + rsp = self.llm.ChatCompletion.create(**self._func_configs(messages, **kwargs)) + self._update_costs(rsp.get("usage")) + return rsp + + async def _achat_completion_function(self, messages: list[dict], **chat_configs) -> dict: + rsp = await self.llm.ChatCompletion.acreate(**self._func_configs(messages, **chat_configs)) + self._update_costs(rsp.get("usage")) + return rsp + + def ask_code(self, messages: list[dict], **kwargs) -> dict: + """Use function of tools to ask a code. + https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools + + Examples: + + >>> llm = OpenAIGPTAPI() + >>> msg = [{'role': 'user', 'content': "Write a python hello world code."}] + >>> llm.ask_code(msg) + {'language': 'python', 'code': "print('Hello, World!')"} + """ + rsp = self._chat_completion_function(messages, **kwargs) + return self.get_choice_function_arguments(rsp) + + async def aask_code(self, messages: list[dict], **kwargs) -> dict: + """Use function of tools to ask a code. + https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools + + Examples: + + >>> llm = OpenAIGPTAPI() + >>> msg = [{'role': 'user', 'content': "Write a python hello world code."}] + >>> rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + """ + rsp = await self._achat_completion_function(messages, **kwargs) + return self.get_choice_function_arguments(rsp) + def _calc_usage(self, messages: list[dict], rsp: str) -> dict: usage = {} if CONFIG.calc_usage: diff --git a/metagpt/utils/function_schema.py b/metagpt/utils/function_schema.py new file mode 100644 index 000000000..9d7cef927 --- /dev/null +++ b/metagpt/utils/function_schema.py @@ -0,0 +1,29 @@ +# function in tools, https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools +general_function_schema = { + "name": "execute", + "description": "Executes code on the user's machine, **in the users local environment**, and returns the output", + "parameters": { + "type": "object", + "properties": { + "language": { + "type": "string", + "description": "The programming language (required parameter to the `execute` function)", + "enum": [ + "python", + "R", + "shell", + "applescript", + "javascript", + "html", + "powershell", + ], + }, + "code": {"type": "string", "description": "The code to execute (required)"}, + }, + "required": ["language", "code"], + }, +} + +# tool_choice value for general_function_schema +# https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice +general_tool_choice = {"type": "function", "function": {"name": "execute"}} diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py new file mode 100644 index 000000000..4cbc896e0 --- /dev/null +++ b/tests/metagpt/provider/test_openai.py @@ -0,0 +1,22 @@ +import pytest + +from metagpt.provider.openai_api import OpenAIGPTAPI + + +@pytest.mark.asyncio +async def test_aask_code(): + llm = OpenAIGPTAPI() + msg = [{"role": "user", "content": "Write a python hello world code."}] + rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 + + +def test_ask_code(): + llm = OpenAIGPTAPI() + msg = [{"role": "user", "content": "Write a python hello world code."}] + rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 From aca50086c3e51c153cf24a777bd9af53030a8ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Sat, 18 Nov 2023 16:59:14 +0800 Subject: [PATCH 20/53] chore: add comment for general_function_schema. --- metagpt/utils/function_schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/metagpt/utils/function_schema.py b/metagpt/utils/function_schema.py index 9d7cef927..f9b84fc60 100644 --- a/metagpt/utils/function_schema.py +++ b/metagpt/utils/function_schema.py @@ -1,4 +1,5 @@ # function in tools, https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools +# Reference: https://github.com/KillianLucas/open-interpreter/blob/v0.1.14/interpreter/llm/setup_openai_coding_llm.py general_function_schema = { "name": "execute", "description": "Executes code on the user's machine, **in the users local environment**, and returns the output", From 2a881589330a86c3c7c7fdd5d608d20679bc6cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Sat, 18 Nov 2023 18:14:38 +0800 Subject: [PATCH 21/53] chore: remove and renamed function_schema.py --- metagpt/{utils/function_schema.py => provider/constant.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename metagpt/{utils/function_schema.py => provider/constant.py} (100%) diff --git a/metagpt/utils/function_schema.py b/metagpt/provider/constant.py similarity index 100% rename from metagpt/utils/function_schema.py rename to metagpt/provider/constant.py From 218eeef4b8616a47b190531db34cbd5cee26e57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Sat, 18 Nov 2023 18:15:50 +0800 Subject: [PATCH 22/53] chore: messages supports more types. --- metagpt/provider/openai_api.py | 22 +++++++++++++-- tests/metagpt/provider/test_openai.py | 39 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index b9698e77d..287668c83 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -21,7 +21,8 @@ from tenacity import ( from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI -from metagpt.utils.function_schema import general_function_schema, general_tool_choice +from metagpt.provider.constant import general_function_schema, general_tool_choice +from metagpt.schema import Message from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( TOKEN_COSTS, @@ -261,7 +262,22 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): self._update_costs(rsp.get("usage")) return rsp - def ask_code(self, messages: list[dict], **kwargs) -> dict: + def _process_message(self, messages: Union[str, Message, list[dict]]) -> list[dict]: + """convert messages to list[dict].""" + if isinstance(messages, list): + return messages + + if isinstance(messages, Message): + messages = [messages.to_dict()] + elif isinstance(messages, str): + messages = [{"role": "user", "content": messages}] + else: + raise ValueError( + f"Only support messages type are: str, Message, list[dict], but got {type(messages).__name__}!" + ) + return messages + + def ask_code(self, messages: Union[str, Message, list[dict]], **kwargs) -> dict: """Use function of tools to ask a code. https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools @@ -272,6 +288,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): >>> llm.ask_code(msg) {'language': 'python', 'code': "print('Hello, World!')"} """ + messages = self._process_message(messages) rsp = self._chat_completion_function(messages, **kwargs) return self.get_choice_function_arguments(rsp) @@ -285,6 +302,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): >>> msg = [{'role': 'user', 'content': "Write a python hello world code."}] >>> rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} """ + messages = self._process_message(messages) rsp = await self._achat_completion_function(messages, **kwargs) return self.get_choice_function_arguments(rsp) diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py index 4cbc896e0..d6d9f4f9d 100644 --- a/tests/metagpt/provider/test_openai.py +++ b/tests/metagpt/provider/test_openai.py @@ -1,6 +1,7 @@ import pytest from metagpt.provider.openai_api import OpenAIGPTAPI +from metagpt.schema import UserMessage @pytest.mark.asyncio @@ -13,6 +14,26 @@ async def test_aask_code(): assert len(rsp["code"]) > 0 +@pytest.mark.asyncio +async def test_aask_code_str(): + llm = OpenAIGPTAPI() + msg = "Write a python hello world code." + rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 + + +@pytest.mark.asyncio +async def test_aask_code_Message(): + llm = OpenAIGPTAPI() + msg = UserMessage("Write a python hello world code.") + rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 + + def test_ask_code(): llm = OpenAIGPTAPI() msg = [{"role": "user", "content": "Write a python hello world code."}] @@ -20,3 +41,21 @@ def test_ask_code(): assert "language" in rsp assert "code" in rsp assert len(rsp["code"]) > 0 + + +def test_ask_code_str(): + llm = OpenAIGPTAPI() + msg = "Write a python hello world code." + rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 + + +def test_ask_code_Message(): + llm = OpenAIGPTAPI() + msg = UserMessage("Write a python hello world code.") + rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 From 62254a184e485ab5bb5219b84c40660b56b216a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Sat, 18 Nov 2023 18:33:00 +0800 Subject: [PATCH 23/53] chore: add examples for ask_code and aask_code. --- metagpt/provider/openai_api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 287668c83..8f8d45d73 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -284,6 +284,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): Examples: >>> llm = OpenAIGPTAPI() + >>> llm.ask_code("Write a python hello world code.") + {'language': 'python', 'code': "print('Hello, World!')"} >>> msg = [{'role': 'user', 'content': "Write a python hello world code."}] >>> llm.ask_code(msg) {'language': 'python', 'code': "print('Hello, World!')"} @@ -292,13 +294,16 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): rsp = self._chat_completion_function(messages, **kwargs) return self.get_choice_function_arguments(rsp) - async def aask_code(self, messages: list[dict], **kwargs) -> dict: + async def aask_code(self, messages: Union[str, Message, list[dict]], **kwargs) -> dict: """Use function of tools to ask a code. https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools Examples: >>> llm = OpenAIGPTAPI() + >>> rsp = await llm.ask_code("Write a python hello world code.") + >>> rsp + {'language': 'python', 'code': "print('Hello, World!')"} >>> msg = [{'role': 'user', 'content': "Write a python hello world code."}] >>> rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"} """ From 96ce036bd4b6fd01402a11e1e9f5abb274e93e77 Mon Sep 17 00:00:00 2001 From: better629 Date: Sat, 18 Nov 2023 21:34:14 +0800 Subject: [PATCH 24/53] add zhipuai api with extra async invoke methods --- metagpt/config.py | 9 +- metagpt/llm.py | 26 ++-- metagpt/provider/zhipuai/__init__.py | 3 + metagpt/provider/zhipuai/async_sse_client.py | 77 ++++++++++ metagpt/provider/zhipuai/zhipu_model_api.py | 76 ++++++++++ metagpt/provider/zhipuai_api.py | 139 +++++++++++++++++++ metagpt/utils/token_counter.py | 6 +- 7 files changed, 323 insertions(+), 13 deletions(-) create mode 100644 metagpt/provider/zhipuai/__init__.py create mode 100644 metagpt/provider/zhipuai/async_sse_client.py create mode 100644 metagpt/provider/zhipuai/zhipu_model_api.py create mode 100644 metagpt/provider/zhipuai_api.py diff --git a/metagpt/config.py b/metagpt/config.py index 27455d38d..3f9e742bd 100644 --- a/metagpt/config.py +++ b/metagpt/config.py @@ -45,10 +45,11 @@ class Config(metaclass=Singleton): self.global_proxy = self._get("GLOBAL_PROXY") self.openai_api_key = self._get("OPENAI_API_KEY") self.anthropic_api_key = self._get("Anthropic_API_KEY") - if (not self.openai_api_key or "YOUR_API_KEY" == self.openai_api_key) and ( - not self.anthropic_api_key or "YOUR_API_KEY" == self.anthropic_api_key - ): - raise NotConfiguredException("Set OPENAI_API_KEY or Anthropic_API_KEY first") + self.zhipuai_api_key = self._get("ZHIPUAI_API_KEY") + if (not self.openai_api_key or "YOUR_API_KEY" == self.openai_api_key) and \ + (not self.anthropic_api_key or "YOUR_API_KEY" == self.anthropic_api_key) and \ + (not self.zhipuai_api_key or "YOUR_API_KEY" == self.zhipuai_api_key): + raise NotConfiguredException("Set OPENAI_API_KEY or Anthropic_API_KEY or ZHIPUAI_API_KEY first") self.openai_api_base = self._get("OPENAI_API_BASE") openai_proxy = self._get("OPENAI_PROXY") or self.global_proxy if openai_proxy: diff --git a/metagpt/llm.py b/metagpt/llm.py index e6f815950..1f6a6bb1a 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -6,14 +6,24 @@ @File : llm.py """ +from metagpt.logs import logger +from metagpt.config import CONFIG from metagpt.provider.anthropic_api import Claude2 as Claude -from metagpt.provider.openai_api import OpenAIGPTAPI as LLM +from metagpt.provider.openai_api import OpenAIGPTAPI +from metagpt.provider.zhipuai_api import ZhiPuAIGPTAPI +from metagpt.provider.spark_api import SparkAPI -DEFAULT_LLM = LLM() -CLAUDE_LLM = Claude() -async def ai_func(prompt): - """使用LLM进行QA - QA with LLMs - """ - return await DEFAULT_LLM.aask(prompt) +def LLM(): + """ initialize different LLM instance according to the key field existence""" + # TODO a little trick, can use registry to initialize LLM instance further + if CONFIG.openai_api_key and CONFIG.openai_api_key.starswith("sk-"): + llm = OpenAIGPTAPI() + elif CONFIG.claude_api_key: + llm = Claude() + elif CONFIG.spark_api_key: + llm = SparkAPI() + elif CONFIG.zhipuai_api_key: + llm = ZhiPuAIGPTAPI() + + return llm diff --git a/metagpt/provider/zhipuai/__init__.py b/metagpt/provider/zhipuai/__init__.py new file mode 100644 index 000000000..2bcf8efd0 --- /dev/null +++ b/metagpt/provider/zhipuai/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/metagpt/provider/zhipuai/async_sse_client.py b/metagpt/provider/zhipuai/async_sse_client.py new file mode 100644 index 000000000..7a4275982 --- /dev/null +++ b/metagpt/provider/zhipuai/async_sse_client.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : async_sse_client to make keep the use of Event to access response + +from zhipuai.utils.sse_client import SSEClient, Event, _FIELD_SEPARATOR + + +class AsyncSSEClient(SSEClient): + + async def _aread(self): + data = b"" + async for chunk in self._event_source: + for line in chunk.splitlines(True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + async def async_events(self): + async for chunk in self._aread(): + event = Event() + # Split before decoding so splitlines() only uses \r and \n + for line in chunk.splitlines(): + # Decode the line. + line = line.decode(self._char_enc) + + # Lines starting with a separator are comments and are to be + # ignored. + if not line.strip() or line.startswith(_FIELD_SEPARATOR): + continue + + data = line.split(_FIELD_SEPARATOR, 1) + field = data[0] + + # Ignore unknown fields. + if field not in event.__dict__: + self._logger.debug( + "Saw invalid field %s while parsing " "Server Side Event", field + ) + continue + + if len(data) > 1: + # From the spec: + # "If value starts with a single U+0020 SPACE character, + # remove it from value." + if data[1].startswith(" "): + value = data[1][1:] + else: + value = data[1] + else: + # If no value is present after the separator, + # assume an empty value. + value = "" + + # The data field may come over multiple lines and their values + # are concatenated with each other. + if field == "data": + event.__dict__[field] += value + "\n" + else: + event.__dict__[field] = value + + # Events with no data are not dispatched. + if not event.data: + continue + + # If the data field ends with a newline, remove it. + if event.data.endswith("\n"): + event.data = event.data[0:-1] + + # Empty event names default to 'message' + event.event = event.event or "message" + + # Dispatch the event + self._logger.debug("Dispatching %s...", event) + yield event diff --git a/metagpt/provider/zhipuai/zhipu_model_api.py b/metagpt/provider/zhipuai/zhipu_model_api.py new file mode 100644 index 000000000..f1fd6f2e2 --- /dev/null +++ b/metagpt/provider/zhipuai/zhipu_model_api.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : zhipu model api to support sync & async for invoke & sse_invoke + +import zhipuai +from zhipuai.model_api.api import ModelAPI, InvokeType +from zhipuai.utils.http_client import headers as zhipuai_default_headers +from zhipuai.utils.sse_client import SSEClient + +from metagpt.provider.zhipuai.async_sse_client import AsyncSSEClient +from metagpt.provider.general_api_requestor import GeneralAPIRequestor + + +class ZhiPuModelAPI(ModelAPI): + + @classmethod + def get_header(cls) -> dict: + token = cls._generate_token() + zhipuai_default_headers.update({"Authorization": token}) + return zhipuai_default_headers + + @classmethod + def get_sse_header(cls) -> dict: + token = cls._generate_token() + headers = { + "Authorization": token + } + return headers + + @classmethod + def split_zhipu_api_url(cls, invoke_type: InvokeType, kwargs): + # use this method to prevent zhipu api upgrading to different version. + zhipu_api_url = cls._build_api_url(kwargs, invoke_type) + # example: https://open.bigmodel.cn/api/paas/v3/model-api/{model}/{invoke_method} + arr = zhipu_api_url.split("/api/") + # ("https://open.bigmodel.cn/api/" , "/paas/v3/model-api/chatglm_turbo/invoke") + return f"{arr[0]}/api", f"/{arr[1]}" + + @classmethod + async def arequest(cls, invoke_type: InvokeType, stream: bool, method: str, headers: dict, kwargs): + # TODO to make the async request to be more generic for models in http mode. + assert method in ["post", "get"] + + api_base, url = cls.split_zhipu_api_url(invoke_type, kwargs) + requester = GeneralAPIRequestor(api_base=api_base) + result, _, api_key = await requester.arequest( + method=method, + url=url, + headers=headers, + stream=stream, + params=kwargs, + request_timeout=zhipuai.api_timeout_seconds + ) + + return result + + @classmethod + async def ainvoke(cls, **kwargs) -> dict: + """ async invoke different from raw method `async_invoke` which get the final result by task_id""" + headers = cls.get_header() + resp = await cls.arequest(invoke_type=InvokeType.SYNC, + stream=False, + method="post", + headers=headers, + kwargs=kwargs) + return resp + + @classmethod + async def asse_invoke(cls, **kwargs) -> AsyncSSEClient: + """ async sse_invoke """ + headers = cls.get_sse_header() + return AsyncSSEClient(await cls.arequest(invoke_type=InvokeType.SSE, + stream=True, + method="post", + headers=headers, + kwargs=kwargs)) diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py new file mode 100644 index 000000000..4e8e6b760 --- /dev/null +++ b/metagpt/provider/zhipuai_api.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : zhipuai LLM from https://open.bigmodel.cn/dev/api#sdk + +from enum import Enum +import json +from tenacity import ( + after_log, + retry, + retry_if_exception_type, + stop_after_attempt, + wait_fixed, +) +from requests import ConnectionError + +import zhipuai + +from metagpt.config import CONFIG +from metagpt.logs import logger +from metagpt.provider.base_gpt_api import BaseGPTAPI +from metagpt.provider.openai_api import CostManager, log_and_reraise +from metagpt.provider.zhipuai.zhipu_model_api import ZhiPuModelAPI +from metagpt.utils.ahttp_client import astream + + +class ZhiPuEvent(Enum): + ADD = "add" + ERROR = "error" + INTERRUPTED = "interrupted" + FINISH = "finish" + + +class ZhiPuAIGPTAPI(BaseGPTAPI): + """ + Refs to `https://open.bigmodel.cn/dev/api#chatglm_turbo` + From now, there is only one model named `chatglm_turbo` + """ + + use_system_prompt: bool = False # zhipuai has no system prompt when use api + + def __init__(self): + self.__init_zhipuai(CONFIG) + self.llm = ZhiPuModelAPI + self.model = "chatglm_turbo" # so far only one model, just use it + self._cost_manager = CostManager() + + def __init_zhipuai(self, config: CONFIG): + assert config.zhipuai_api_key + zhipuai.api_key = config.zhipuai_api_key + + def _const_kwargs(self, messages: list[dict]) -> dict: + kwargs = { + "model": self.model, + "prompt": messages, + "temperature": 0.3 + } + return kwargs + + def _update_costs(self, usage: dict): + """ update each request's token cost """ + if CONFIG.calc_usage: + try: + prompt_tokens = int(usage.get("prompt_tokens", 0)) + completion_tokens = int(usage.get("completion_tokens", 0)) + self._cost_manager.update_cost(prompt_tokens, completion_tokens, self.model) + except Exception as e: + logger.error("zhipuai updats costs failed!", e) + + def get_choice_text(self, resp: dict) -> str: + """ get the first text of choice from llm response """ + assist_msg = resp.get("data").get("choices")[-1] + assert assist_msg["role"] == "assistant" + return assist_msg.get("content") + + def completion(self, messages: list[dict]) -> dict: + resp = self.llm.invoke(**self._const_kwargs(messages)) + usage = resp.get("data").get("usage") + self._update_costs(usage) + return resp + + async def _achat_completion(self, messages: list[dict]) -> dict: + resp = await self.llm.ainvoke(**self._const_kwargs(messages)) + usage = resp.get("data").get("usage") + self._update_costs(usage) + return resp + + async def acompletion(self, messages: list[dict]) -> dict: + return await self._achat_completion(messages) + + async def _achat_completion_stream(self, messages: list[dict]) -> str: + response = await self.llm.asse_invoke(**self._const_kwargs(messages)) + collected_content = [] + usage = {} + async for event in response.async_events(): + if event.event == ZhiPuEvent.ADD.value: + content = event.data + collected_content.append(content) + print(content, end="") + elif event.event == ZhiPuEvent.ERROR.value or event.event == ZhiPuEvent.INTERRUPTED.value: + content = event.data + logger.error(f"event error: {content}", end="") + collected_content.append([content]) + elif event.event == ZhiPuEvent.FINISH.value: + """ + event.meta + { + "task_status":"SUCCESS", + "usage":{ + "completion_tokens":351, + "prompt_tokens":595, + "total_tokens":946 + }, + "task_id":"xx", + "request_id":"xxx" + } + """ + meta = json.loads(event.meta) + usage = meta.get("usage") + else: + print(f"zhipuapi else event: {event.data}", end="") + + self._update_costs(usage) + full_content = "".join(collected_content) + logger.info(f"full_content: {full_content} !!") + return full_content + + # @retry( + # stop=stop_after_attempt(3), + # wait=wait_fixed(1), + # after=after_log(logger, logger.level("WARNING").name), + # retry=retry_if_exception_type(ConnectionError), + # retry_error_callback=log_and_reraise + # ) + async def acompletion_text(self, messages: list[dict], stream=False) -> str: + """ response in async with stream or non-stream mode """ + if stream: + return await self._achat_completion_stream(messages) + resp = await self._achat_completion(messages) + return self.get_choice_text(resp) diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index a5a65803a..1af96f272 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -22,6 +22,7 @@ TOKEN_COSTS = { "gpt-4-32k-0314": {"prompt": 0.06, "completion": 0.12}, "gpt-4-0613": {"prompt": 0.06, "completion": 0.12}, "text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0}, + "chatglm_turbo": {"prompt": 0.0, "completion": 0.00069} # 32k version, prompt + completion tokens=0.005¥/k-tokens } @@ -37,6 +38,7 @@ TOKEN_MAX = { "gpt-4-32k-0314": 32768, "gpt-4-0613": 8192, "text-embedding-ada-002": 8192, + "chatglm_turbo": 32768 } @@ -68,7 +70,9 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0613"): return count_message_tokens(messages, model="gpt-4-0613") else: raise NotImplementedError( - f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""" + f"num_tokens_from_messages() is not implemented for model {model}. " + f"See https://github.com/openai/openai-python/blob/main/chatml.md " + f"for information on how messages are converted to tokens." ) num_tokens = 0 for message in messages: From 66f27ca2d599c7e6420ac11d4f44c2d4668d5d28 Mon Sep 17 00:00:00 2001 From: better629 Date: Sat, 18 Nov 2023 21:35:41 +0800 Subject: [PATCH 25/53] add General Async API for http-based LLM model --- metagpt/provider/general_api_requestor.py | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 metagpt/provider/general_api_requestor.py diff --git a/metagpt/provider/general_api_requestor.py b/metagpt/provider/general_api_requestor.py new file mode 100644 index 000000000..e4e5f0f96 --- /dev/null +++ b/metagpt/provider/general_api_requestor.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : General Async API for http-based LLM model + +from typing import AsyncGenerator, Tuple, Union, Optional, Literal +import aiohttp +import asyncio + +from openai.api_requestor import APIRequestor, aiohttp_session + +from metagpt.logs import logger + + +class GeneralAPIRequestor(APIRequestor): + """ + usage + # full_url = "{api_base}{url}" + requester = GeneralAPIRequestor(api_base=api_base) + result, _, api_key = await requester.arequest( + method=method, + url=url, + headers=headers, + stream=stream, + params=kwargs, + request_timeout=120 + ) + """ + + def _interpret_response_line( + self, rbody: str, rcode: int, rheaders, stream: bool + ) -> str: + # just do nothing to meet the APIRequestor process and return the raw data + # due to the openai sdk will convert the data into OpenAIResponse which we don't need in general cases. + + return rbody + + async def _interpret_async_response( + self, result: aiohttp.ClientResponse, stream: bool + ) -> Tuple[Union[str, AsyncGenerator[str, None]], bool]: + if stream and "text/event-stream" in result.headers.get("Content-Type", ""): + logger.warning("stream") + return ( + self._interpret_response_line( + line, result.status, result.headers, stream=True + ) + async for line in result.content + ), True + else: + logger.warning("non stream") + try: + await result.read() + except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e: + raise TimeoutError("Request timed out") from e + except aiohttp.ClientError as exp: + logger.warning(f"response: {result.content}, exp: {exp}") + return ( + self._interpret_response_line( + await result.read(), # let the caller to decode the msg + result.status, + result.headers, + stream=False, + ), + False, + ) From 8e201384bf67020e6cf8efcf248dc2fd71977e57 Mon Sep 17 00:00:00 2001 From: better629 Date: Sat, 18 Nov 2023 21:28:49 +0800 Subject: [PATCH 26/53] add use_system_prompt to judge if need to add system_prompt part --- config/config.yaml | 3 +++ metagpt/provider/base_chatbot.py | 1 + metagpt/provider/base_gpt_api.py | 8 +++++--- requirements.txt | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index b2c50991d..fc6961f9e 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -31,6 +31,9 @@ RPM: 10 #DEPLOYMENT_NAME: "YOUR_DEPLOYMENT_NAME" #DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" +#### if zhipuai from `https://open.bigmodel.cn`. You can set here or export API_KEY="YOUR_API_KEY" +ZHIPUAI_API_KEY: "YOUR_API_KEY" + #### for Search ## Supported values: serpapi/google/serper/ddg diff --git a/metagpt/provider/base_chatbot.py b/metagpt/provider/base_chatbot.py index abdf423f4..72e6c94f9 100644 --- a/metagpt/provider/base_chatbot.py +++ b/metagpt/provider/base_chatbot.py @@ -13,6 +13,7 @@ from dataclasses import dataclass class BaseChatbot(ABC): """Abstract GPT class""" mode: str = "API" + use_system_prompt: bool = True @abstractmethod def ask(self, msg: str) -> str: diff --git a/metagpt/provider/base_gpt_api.py b/metagpt/provider/base_gpt_api.py index de61167b9..3a157b63e 100644 --- a/metagpt/provider/base_gpt_api.py +++ b/metagpt/provider/base_gpt_api.py @@ -32,15 +32,17 @@ class BaseGPTAPI(BaseChatbot): return self._system_msg(self.system_prompt) def ask(self, msg: str) -> str: - message = [self._default_system_msg(), self._user_msg(msg)] + message = [self._default_system_msg(), self._user_msg(msg)] if self.use_system_prompt else [self._user_msg(msg)] rsp = self.completion(message) return self.get_choice_text(rsp) async def aask(self, msg: str, system_msgs: Optional[list[str]] = None) -> str: if system_msgs: - message = self._system_msgs(system_msgs) + [self._user_msg(msg)] + message = self._system_msgs(system_msgs) + [self._user_msg(msg)] if self.use_system_prompt \ + else [self._user_msg(msg)] else: - message = [self._default_system_msg(), self._user_msg(msg)] + message = [self._default_system_msg(), self._user_msg(msg)] if self.use_system_prompt \ + else [self._user_msg(msg)] rsp = await self.acompletion_text(message, stream=True) logger.debug(message) # logger.debug(rsp) diff --git a/requirements.txt b/requirements.txt index 093298775..66e8dce02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,3 +44,4 @@ ta==0.10.2 semantic-kernel==0.3.13.dev0 wrapt==1.15.0 websocket-client==0.58.0 +zhipuai==1.0.7 From 2c81cc3e0f976d9b3774761d72036c60aa824866 Mon Sep 17 00:00:00 2001 From: better629 Date: Sat, 18 Nov 2023 22:00:52 +0800 Subject: [PATCH 27/53] add zhipuai_api unittest and remove useless log --- metagpt/provider/general_api_requestor.py | 2 - metagpt/provider/zhipuai/zhipu_model_api.py | 1 - tests/metagpt/provider/test_zhipuai_api.py | 47 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 tests/metagpt/provider/test_zhipuai_api.py diff --git a/metagpt/provider/general_api_requestor.py b/metagpt/provider/general_api_requestor.py index e4e5f0f96..169b7c146 100644 --- a/metagpt/provider/general_api_requestor.py +++ b/metagpt/provider/general_api_requestor.py @@ -38,7 +38,6 @@ class GeneralAPIRequestor(APIRequestor): self, result: aiohttp.ClientResponse, stream: bool ) -> Tuple[Union[str, AsyncGenerator[str, None]], bool]: if stream and "text/event-stream" in result.headers.get("Content-Type", ""): - logger.warning("stream") return ( self._interpret_response_line( line, result.status, result.headers, stream=True @@ -46,7 +45,6 @@ class GeneralAPIRequestor(APIRequestor): async for line in result.content ), True else: - logger.warning("non stream") try: await result.read() except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e: diff --git a/metagpt/provider/zhipuai/zhipu_model_api.py b/metagpt/provider/zhipuai/zhipu_model_api.py index f1fd6f2e2..e1d52061d 100644 --- a/metagpt/provider/zhipuai/zhipu_model_api.py +++ b/metagpt/provider/zhipuai/zhipu_model_api.py @@ -5,7 +5,6 @@ import zhipuai from zhipuai.model_api.api import ModelAPI, InvokeType from zhipuai.utils.http_client import headers as zhipuai_default_headers -from zhipuai.utils.sse_client import SSEClient from metagpt.provider.zhipuai.async_sse_client import AsyncSSEClient from metagpt.provider.general_api_requestor import GeneralAPIRequestor diff --git a/tests/metagpt/provider/test_zhipuai_api.py b/tests/metagpt/provider/test_zhipuai_api.py new file mode 100644 index 000000000..6a0c70de5 --- /dev/null +++ b/tests/metagpt/provider/test_zhipuai_api.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : the unittest of ZhiPuAIGPTAPI + +import pytest + +from metagpt.provider.zhipuai_api import ZhiPuAIGPTAPI + + +default_resp = { + "code": 200, + "data": { + "choices": [ + {"role": "assistant", "content": "I'm chatglm-turbo"} + ] + } +} + +messages = [ + {"role": "user", "content": "who are you"} +] + + +def mock_llm_ask(self, messages: list[dict]) -> dict: + return default_resp + + +def test_zhipuai_completion(mocker): + mocker.patch("metagpt.provider.zhipuai_api.ZhiPuAIGPTAPI.completion", mock_llm_ask) + + resp = ZhiPuAIGPTAPI().completion(messages) + assert resp["code"] == 200 + assert "chatglm-turbo" in resp["data"]["choices"][0]["content"] + + +async def mock_llm_aask(self, messgaes: list[dict], stream: bool = False) -> dict: + return default_resp + + +@pytest.mark.asyncio +async def test_zhipuai_acompletion(mocker): + mocker.patch("metagpt.provider.zhipuai_api.ZhiPuAIGPTAPI.acompletion_text", mock_llm_aask) + + resp = await ZhiPuAIGPTAPI().acompletion_text(messages, stream=False) + + assert resp["code"] == 200 + assert "chatglm-turbo" in resp["data"]["choices"][0]["content"] From 6ef3b213c3504a3d2e5f4f5ffc04b47e70dc78fd Mon Sep 17 00:00:00 2001 From: better629 Date: Sat, 18 Nov 2023 22:17:40 +0800 Subject: [PATCH 28/53] fix small problem --- metagpt/llm.py | 2 +- metagpt/provider/zhipuai/async_sse_client.py | 1 + metagpt/provider/zhipuai/zhipu_model_api.py | 6 +++++- metagpt/provider/zhipuai_api.py | 17 ++++++++--------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/metagpt/llm.py b/metagpt/llm.py index 1f6a6bb1a..e9b80d7a8 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -14,7 +14,7 @@ from metagpt.provider.zhipuai_api import ZhiPuAIGPTAPI from metagpt.provider.spark_api import SparkAPI -def LLM(): +def LLM() -> "BaseGPTAPI": """ initialize different LLM instance according to the key field existence""" # TODO a little trick, can use registry to initialize LLM instance further if CONFIG.openai_api_key and CONFIG.openai_api_key.starswith("sk-"): diff --git a/metagpt/provider/zhipuai/async_sse_client.py b/metagpt/provider/zhipuai/async_sse_client.py index 7a4275982..b819fdc63 100644 --- a/metagpt/provider/zhipuai/async_sse_client.py +++ b/metagpt/provider/zhipuai/async_sse_client.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # @Desc : async_sse_client to make keep the use of Event to access response +# refs to `https://github.com/zhipuai/zhipuai-sdk-python/blob/main/zhipuai/utils/sse_client.py` from zhipuai.utils.sse_client import SSEClient, Event, _FIELD_SEPARATOR diff --git a/metagpt/provider/zhipuai/zhipu_model_api.py b/metagpt/provider/zhipuai/zhipu_model_api.py index e1d52061d..618b2e865 100644 --- a/metagpt/provider/zhipuai/zhipu_model_api.py +++ b/metagpt/provider/zhipuai/zhipu_model_api.py @@ -29,8 +29,12 @@ class ZhiPuModelAPI(ModelAPI): @classmethod def split_zhipu_api_url(cls, invoke_type: InvokeType, kwargs): # use this method to prevent zhipu api upgrading to different version. + # and follow the GeneralAPIRequestor implemented based on openai sdk zhipu_api_url = cls._build_api_url(kwargs, invoke_type) - # example: https://open.bigmodel.cn/api/paas/v3/model-api/{model}/{invoke_method} + """ + example: + zhipu_api_url: https://open.bigmodel.cn/api/paas/v3/model-api/{model}/{invoke_method} + """ arr = zhipu_api_url.split("/api/") # ("https://open.bigmodel.cn/api/" , "/paas/v3/model-api/chatglm_turbo/invoke") return f"{arr[0]}/api", f"/{arr[1]}" diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py index 4e8e6b760..2ad1944c2 100644 --- a/metagpt/provider/zhipuai_api.py +++ b/metagpt/provider/zhipuai_api.py @@ -68,7 +68,7 @@ class ZhiPuAIGPTAPI(BaseGPTAPI): def get_choice_text(self, resp: dict) -> str: """ get the first text of choice from llm response """ - assist_msg = resp.get("data").get("choices")[-1] + assist_msg = resp.get("data", {}).get("choices", [{"role": "error"}])[-1] assert assist_msg["role"] == "assistant" return assist_msg.get("content") @@ -121,16 +121,15 @@ class ZhiPuAIGPTAPI(BaseGPTAPI): self._update_costs(usage) full_content = "".join(collected_content) - logger.info(f"full_content: {full_content} !!") return full_content - # @retry( - # stop=stop_after_attempt(3), - # wait=wait_fixed(1), - # after=after_log(logger, logger.level("WARNING").name), - # retry=retry_if_exception_type(ConnectionError), - # retry_error_callback=log_and_reraise - # ) + @retry( + stop=stop_after_attempt(3), + wait=wait_fixed(1), + after=after_log(logger, logger.level("WARNING").name), + retry=retry_if_exception_type(ConnectionError), + retry_error_callback=log_and_reraise + ) async def acompletion_text(self, messages: list[dict], stream=False) -> str: """ response in async with stream or non-stream mode """ if stream: From ef5cb6f6deaf5f3215a895b592ce424e125ef38b Mon Sep 17 00:00:00 2001 From: better629 Date: Sun, 19 Nov 2023 15:38:47 +0800 Subject: [PATCH 29/53] update readme with online docs url --- README.md | 32 ++++++++++++++++++-------------- docs/README_CN.md | 30 ++++++++++++++++-------------- docs/examples/README.md | 12 ------------ 3 files changed, 34 insertions(+), 40 deletions(-) delete mode 100644 docs/examples/README.md diff --git a/README.md b/README.md index f3fb3dff9..60193e086 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ # MetaGPT: The Multi-Agent Framework

Software Company Multi-Role Schematic (Gradually Implementing)

+ + ## Install ### Conda installation @@ -58,7 +60,7 @@ # setup your OPENAI_API_KEY in key.yaml copy from config.yaml python3 startup.py "Write a cli snake game" ``` -detail installation please refer to [cli_install](docs/install/cli_install.md) +detail installation please refer to [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#installation-for-full-features) ### Docker installation @@ -78,28 +80,30 @@ # Step 2: Run metagpt demo with container python startup.py "Write a cli snake game" ``` -detail installation please refer to [docker_install](docs/install/docker_install.md) +detail installation please refer to [docker_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-with-docker) ### QuickStart & Demo Video - Try it on [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) - [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) - [Official Demo Video](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d) +https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419 + ## Tutorial -- [Oneline Document]() -- [Usage](docs/tutorial/usage.md) -- [What can MetaGPT do?](docs/tutorial/what_can_this_do.md) -- How to build your own agents? - - [MetaGPT Usage & Development Guide](https://deepwisdom.feishu.cn/wiki/RUnswqUIPimRJmkkDZ7cLYwOndg#Yu2AdUvymoo67Jxbp0bcu8G4nEb) -- [Contribution](docs/develop/contribution.md) - - Develop RFC +- 🗒 [Online Document](https://docs.deepwisdom.ai/) +- 💻 [Usage](https://docs.deepwisdom.ai/guide/get_started/quickstart.html) +- 🔎 [What can MetaGPT do?](https://docs.deepwisdom.ai/guide/get_started/introduction.html) +- 🛠 How to build your own agents? + - [MetaGPT Usage & Development Guide | Agent 101](https://docs.deepwisdom.ai/guide/tutorials/agent_101.html) + - [MetaGPT Usage & Development Guide | MultiAgent 101](https://docs.deepwisdom.ai/guide/tutorials/multi_agent_101.html) +- 🧑‍💻 Contribution - [Develop Roadmap](docs/ROADMAP.md) -- [Examples](docs/examples/README.md) - - Researcher - - Werewolf Game -- [FAQs](docs/tutorial/faq.md) -- [The generated projects display wall](https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419) +- 🔖 Use Cases + - [Debate](https://docs.deepwisdom.ai/guide/use_cases/multi_agent/debate.html) + - [Researcher](https://docs.deepwisdom.ai/guide/use_cases/agent/researcher.html) + - [Recepit Assistant](https://docs.deepwisdom.ai/guide/use_cases/agent/receipt_assistant.html) +- ❓ [FAQs](https://docs.deepwisdom.ai/guide/faq.html) ## Support diff --git a/docs/README_CN.md b/docs/README_CN.md index 8c04e5066..6dac0e210 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -57,7 +57,7 @@ # 拷贝config.yaml为key.yaml,并设置你自己的OPENAI_API_KEY python3 startup.py "Write a cli snake game" ``` -详细的安装请安装 [cli_install](docs/install/cli_install_cn.md) +详细的安装请安装 [cli_install](https://docs.deepwisdom.ai/zhcn/guide/get_started/installation.html#%E5%AE%89%E8%A3%85%E5%85%A8%E9%83%A8%E5%8A%9F%E8%83%BD) ### Docker安装 @@ -77,27 +77,29 @@ # 步骤2: 使用容器运行metagpt演示 python startup.py "Write a cli snake game" ``` -详细的安装请安装 [docker_install](docs/install/docker_install_cn.md) +详细的安装请安装 [docker_install](https://docs.deepwisdom.ai/zhcn/guide/get_started/installation.html#%E4%BD%BF%E7%94%A8docker%E5%AE%89%E8%A3%85) ### 快速开始的演示视频 - 在 [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) 上进行体验 - [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) - [官方演示视频](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d) +https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419 + ## 教程 -- [在线文档]() -- [如何使用](docs/tutorial/usage_cn.md) -- [MetaGPT的能力及应用场景](docs/tutorial/what_can_this_do.md) -- 如何构建你自己得智能体? - - [MetaGPT的使用和开发教程](https://deepwisdom.feishu.cn/wiki/RUnswqUIPimRJmkkDZ7cLYwOndg#Yu2AdUvymoo67Jxbp0bcu8G4nEb) -- [贡献](docs/develop/contribution.md) - - 开发者RFC +- 🗒 [在线文档](https://docs.deepwisdom.ai/zhcn/) +- 💻 [如何使用](https://docs.deepwisdom.ai/zhcn/guide/get_started/quickstart.html) +- 🔎 [MetaGPT的能力及应用场景](https://docs.deepwisdom.ai/zhcn/guide/get_started/introduction.html) +- 🛠 如何构建你自己得智能体? + - [MetaGPT的使用和开发教程 | 智能体入门](https://docs.deepwisdom.ai/zhcn/guide/tutorials/agent_101.html) + - [MetaGPT的使用和开发教程 | 多智能体入门](https://docs.deepwisdom.ai/zhcn/guide/tutorials/multi_agent_101.html) +- 🧑‍💻 贡献 - [开发路线图](docs/ROADMAP.md) -- [样例](docs/examples/README.md) - - 调研员 - - 狼人杀游戏 -- [常见问题解答](docs/tutorial/faq.md) -- [已生成项目的展示墙](https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419) +- 🔖 示例 + - [辩论](https://docs.deepwisdom.ai/zhcn/guide/use_cases/multi_agent/debate.html) + - [调研员](https://docs.deepwisdom.ai/zhcn/guide/use_cases/agent/researcher.html) + - [票据助手](https://docs.deepwisdom.ai/zhcn/guide/use_cases/agent/receipt_assistant.html) +- ❓ [常见问题解答](https://docs.deepwisdom.ai/zhcn/guide/faq.html) ## 支持 diff --git a/docs/examples/README.md b/docs/examples/README.md deleted file mode 100644 index 4fe4628e2..000000000 --- a/docs/examples/README.md +++ /dev/null @@ -1,12 +0,0 @@ -## Examples -We introduce some example agents created recently and show how to created these agents under the framework. - -- Researcher - - With the given research topic, the agent will derive the keywords from the topic and then search the related documents using search engine api. - - The search result will be ranked and filtered to get high quality candidates. - - Summary the final content using the candidate documents as context. -- Invoice OCR and QA - - With the given one or multi invoices, the agent can recognize the text from the image or pdf. - - Organize the text with particular structure, generally, it will be saved in a csv. - - It can answer your question like `what's the total reimbursement of Alice?` -- Werewolf Game From bf47b500bfeeb8b0c53965ce82acf717f058ab56 Mon Sep 17 00:00:00 2001 From: better629 Date: Sun, 19 Nov 2023 15:43:56 +0800 Subject: [PATCH 30/53] fix --- docs/README_CN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README_CN.md b/docs/README_CN.md index 6dac0e210..1839b8f67 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -90,11 +90,11 @@ ## 教程 - 🗒 [在线文档](https://docs.deepwisdom.ai/zhcn/) - 💻 [如何使用](https://docs.deepwisdom.ai/zhcn/guide/get_started/quickstart.html) - 🔎 [MetaGPT的能力及应用场景](https://docs.deepwisdom.ai/zhcn/guide/get_started/introduction.html) -- 🛠 如何构建你自己得智能体? +- 🛠 如何构建你自己的智能体? - [MetaGPT的使用和开发教程 | 智能体入门](https://docs.deepwisdom.ai/zhcn/guide/tutorials/agent_101.html) - [MetaGPT的使用和开发教程 | 多智能体入门](https://docs.deepwisdom.ai/zhcn/guide/tutorials/multi_agent_101.html) - 🧑‍💻 贡献 - - [开发路线图](docs/ROADMAP.md) + - [开发路线图](ROADMAP.md) - 🔖 示例 - [辩论](https://docs.deepwisdom.ai/zhcn/guide/use_cases/multi_agent/debate.html) - [调研员](https://docs.deepwisdom.ai/zhcn/guide/use_cases/agent/researcher.html) From 1b91e66e3aa78099c90239ba6a6f76fcfbc5292e Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 09:23:47 +0800 Subject: [PATCH 31/53] update --- README.md | 8 ++++---- docs/README_CN.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 60193e086..c537e2a3b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ # MetaGPT: The Multi-Agent Framework ## Install -### Conda installation +### Pip installation ```bash # Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) @@ -50,17 +50,17 @@ # conda create -n metagpt python=3.9 # conda activate metagpt python3 --version -# Step 3: Clone the repository to your local machine, and install it. +# Step 3: Clone the repository to your local machine for latest version, and install it. git clone https://github.com/geekan/metagpt cd metagpt -pip3 install -e. +pip3 install -e. # or pip3 install metagpt # for stable version # Step 4: run the startup.py # setup your OPENAI_API_KEY in key.yaml copy from config.yaml python3 startup.py "Write a cli snake game" ``` -detail installation please refer to [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#installation-for-full-features) +detail installation please refer to [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version) ### Docker installation diff --git a/docs/README_CN.md b/docs/README_CN.md index 1839b8f67..3da41793e 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -34,7 +34,7 @@ # MetaGPT: 多智能体框架

软件公司多角色示意图(正在逐步实现)

## 安装 -### Conda安装 +### Pip安装 ```bash # 第 1 步:确保您的系统上安装了 NPM。并使用npm安装mermaid-js @@ -47,17 +47,17 @@ # conda create -n metagpt python=3.9 # conda activate metagpt python --version -# 第 3 步:克隆仓库到您的本地机器,并进行安装。 +# 第 3 步:克隆最新仓库到您的本地机器,并进行安装。 git clone https://github.com/geekan/metagpt cd metagpt -pip install -e. +pip install -e. # 或者 pip3 install metagpt # 安装稳定版本 # 第 4 步:执行startup.py # 拷贝config.yaml为key.yaml,并设置你自己的OPENAI_API_KEY python3 startup.py "Write a cli snake game" ``` -详细的安装请安装 [cli_install](https://docs.deepwisdom.ai/zhcn/guide/get_started/installation.html#%E5%AE%89%E8%A3%85%E5%85%A8%E9%83%A8%E5%8A%9F%E8%83%BD) +详细的安装请安装 [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version) ### Docker安装 From 33ddababfff7e0d4446292d8055fc2bdfe897f7a Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 09:27:22 +0800 Subject: [PATCH 32/53] update --- docs/README_CN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README_CN.md b/docs/README_CN.md index 3da41793e..22e8358b2 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -45,12 +45,12 @@ # 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下 # 可以使用conda来初始化新的python环境 # conda create -n metagpt python=3.9 # conda activate metagpt -python --version +python3 --version # 第 3 步:克隆最新仓库到您的本地机器,并进行安装。 git clone https://github.com/geekan/metagpt cd metagpt -pip install -e. # 或者 pip3 install metagpt # 安装稳定版本 +pip3 install -e. # 或者 pip3 install metagpt # 安装稳定版本 # 第 4 步:执行startup.py # 拷贝config.yaml为key.yaml,并设置你自己的OPENAI_API_KEY From f8f938f333ff781ccc3755c798200df2e1272f87 Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 14:46:31 +0800 Subject: [PATCH 33/53] fix config when open llm model hosts as openai interface --- config/config.yaml | 4 ++-- metagpt/llm.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index fc6961f9e..bed67083c 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -7,7 +7,7 @@ ## Or, you can configure OPENAI_PROXY to access official OPENAI_API_BASE. OPENAI_API_BASE: "https://api.openai.com/v1" #OPENAI_PROXY: "http://127.0.0.1:8118" -#OPENAI_API_KEY: "YOUR_API_KEY" +#OPENAI_API_KEY: "YOUR_API_KEY" # set the value to sk-xxx if you host the openai interface for open llm model OPENAI_API_MODEL: "gpt-4" MAX_TOKENS: 1500 RPM: 10 @@ -32,7 +32,7 @@ RPM: 10 #DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" #### if zhipuai from `https://open.bigmodel.cn`. You can set here or export API_KEY="YOUR_API_KEY" -ZHIPUAI_API_KEY: "YOUR_API_KEY" +# ZHIPUAI_API_KEY: "YOUR_API_KEY" #### for Search diff --git a/metagpt/llm.py b/metagpt/llm.py index e9b80d7a8..13e5a56e0 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -17,13 +17,15 @@ from metagpt.provider.spark_api import SparkAPI def LLM() -> "BaseGPTAPI": """ initialize different LLM instance according to the key field existence""" # TODO a little trick, can use registry to initialize LLM instance further - if CONFIG.openai_api_key and CONFIG.openai_api_key.starswith("sk-"): + if CONFIG.openai_api_key and CONFIG.openai_api_key.startswith("sk-"): llm = OpenAIGPTAPI() elif CONFIG.claude_api_key: llm = Claude() elif CONFIG.spark_api_key: llm = SparkAPI() - elif CONFIG.zhipuai_api_key: + elif CONFIG.zhipuai_api_key and CONFIG.zhipuai_api_key != "YOUR_API_KEY": llm = ZhiPuAIGPTAPI() + else: + raise RuntimeError("You should config a LLM configuration first") return llm From 33f0ca2aee7e90021d914b752aa9cd016f7c5a16 Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 15:04:37 +0800 Subject: [PATCH 34/53] add optional for npm --- README.md | 7 ++++--- docs/README_CN.md | 7 ++++--- docs/README_JA.md | 4 ++-- docs/install/cli_install.md | 4 ++-- docs/install/cli_install_cn.md | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c537e2a3b..1929a8bdd 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ ## Install ### Pip installation ```bash -# Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) +# Step 1 [Optional]: If you want to save the artifacts like PRD in the workspace, you can execute the step. By default, the framework is compatible, and the entire process can be run completely without executing this step. +# If executing, ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) npm --version sudo npm install -g @mermaid-js/mermaid-cli @@ -51,8 +52,8 @@ # conda activate metagpt python3 --version # Step 3: Clone the repository to your local machine for latest version, and install it. -git clone https://github.com/geekan/metagpt -cd metagpt +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT pip3 install -e. # or pip3 install metagpt # for stable version # Step 4: run the startup.py diff --git a/docs/README_CN.md b/docs/README_CN.md index 22e8358b2..3e0879c1b 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -37,7 +37,8 @@ ## 安装 ### Pip安装 ```bash -# 第 1 步:确保您的系统上安装了 NPM。并使用npm安装mermaid-js +# 第 1 步【可选的】:如果你想在执行过程中保存像PRD文件这些产物,可以执行该步骤。默认的,框架做了兼容,在不执行该步的情况下,也可以完整跑完整个流程。 +# 如果执行,确保您的系统上安装了 NPM。并使用npm安装mermaid-js npm --version sudo npm install -g @mermaid-js/mermaid-cli @@ -48,8 +49,8 @@ # conda activate metagpt python3 --version # 第 3 步:克隆最新仓库到您的本地机器,并进行安装。 -git clone https://github.com/geekan/metagpt -cd metagpt +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT pip3 install -e. # 或者 pip3 install metagpt # 安装稳定版本 # 第 4 步:执行startup.py diff --git a/docs/README_JA.md b/docs/README_JA.md index 2b2c35a62..dfb9c8372 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -68,8 +68,8 @@ # ステップ 2: Python 3.9+ がシステムにインストールされてい python --version # ステップ 3: リポジトリをローカルマシンにクローンし、インストールする。 -git clone https://github.com/geekan/metagpt -cd metagpt +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT pip install -e. ``` diff --git a/docs/install/cli_install.md b/docs/install/cli_install.md index 19f0e3c75..80deda771 100644 --- a/docs/install/cli_install.md +++ b/docs/install/cli_install.md @@ -17,8 +17,8 @@ # Step 2: Ensure that Python 3.9+ is installed on your system. You can check thi python3 --version # Step 3: Clone the repository to your local machine, and install it. -git clone https://github.com/geekan/metagpt -cd metagpt +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT pip install -e. ``` diff --git a/docs/install/cli_install_cn.md b/docs/install/cli_install_cn.md index fe97a0b80..f351090ed 100644 --- a/docs/install/cli_install_cn.md +++ b/docs/install/cli_install_cn.md @@ -18,8 +18,8 @@ # 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下 python --version # 第 3 步:克隆仓库到您的本地机器,并进行安装。 -git clone https://github.com/geekan/metagpt -cd metagpt +git clone https://github.com/geekan/MetaGPT.git +cd MetaGPT pip install -e. ``` From 920caa048aaa85eb47503f6c48f50f0a3af16773 Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 15:11:55 +0800 Subject: [PATCH 35/53] change step order of pip-install --- README.md | 16 ++++++++-------- docs/README_CN.md | 16 ++++++++-------- docs/README_JA.md | 17 +++++++++++------ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 1929a8bdd..0606d2be6 100644 --- a/README.md +++ b/README.md @@ -40,25 +40,25 @@ ## Install ### Pip installation ```bash -# Step 1 [Optional]: If you want to save the artifacts like PRD in the workspace, you can execute the step. By default, the framework is compatible, and the entire process can be run completely without executing this step. -# If executing, ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) -npm --version -sudo npm install -g @mermaid-js/mermaid-cli - -# Step 2: Ensure that Python 3.9+ is installed on your system. You can check this by using: +# Step 1: Ensure that Python 3.9+ is installed on your system. You can check this by using: # You can use conda to initialize a new python env # conda create -n metagpt python=3.9 # conda activate metagpt python3 --version -# Step 3: Clone the repository to your local machine for latest version, and install it. +# Step 2: Clone the repository to your local machine for latest version, and install it. git clone https://github.com/geekan/MetaGPT.git cd MetaGPT pip3 install -e. # or pip3 install metagpt # for stable version -# Step 4: run the startup.py +# Step 3: run the startup.py # setup your OPENAI_API_KEY in key.yaml copy from config.yaml python3 startup.py "Write a cli snake game" + +# Step 4 [Optional]: If you want to save the artifacts like PRD in the workspace, you can execute the step before Step 3. By default, the framework is compatible, and the entire process can be run completely without executing this step. +# If executing, ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) +npm --version +sudo npm install -g @mermaid-js/mermaid-cli ``` detail installation please refer to [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version) diff --git a/docs/README_CN.md b/docs/README_CN.md index 3e0879c1b..d38d6908b 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -37,25 +37,25 @@ ## 安装 ### Pip安装 ```bash -# 第 1 步【可选的】:如果你想在执行过程中保存像PRD文件这些产物,可以执行该步骤。默认的,框架做了兼容,在不执行该步的情况下,也可以完整跑完整个流程。 -# 如果执行,确保您的系统上安装了 NPM。并使用npm安装mermaid-js -npm --version -sudo npm install -g @mermaid-js/mermaid-cli - -# 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下命令进行检查: +# 第 1 步:确保您的系统上安装了 Python 3.9+。您可以使用以下命令进行检查: # 可以使用conda来初始化新的python环境 # conda create -n metagpt python=3.9 # conda activate metagpt python3 --version -# 第 3 步:克隆最新仓库到您的本地机器,并进行安装。 +# 第 2 步:克隆最新仓库到您的本地机器,并进行安装。 git clone https://github.com/geekan/MetaGPT.git cd MetaGPT pip3 install -e. # 或者 pip3 install metagpt # 安装稳定版本 -# 第 4 步:执行startup.py +# 第 3 步:执行startup.py # 拷贝config.yaml为key.yaml,并设置你自己的OPENAI_API_KEY python3 startup.py "Write a cli snake game" + +# 第 4 步【可选的】:如果你想在执行过程中保存像PRD文件这些产物,可以在第3步前执行该步骤。默认的,框架做了兼容,在不执行该步的情况下,也可以完整跑完整个流程。 +# 如果执行,确保您的系统上安装了 NPM。并使用npm安装mermaid-js +npm --version +sudo npm install -g @mermaid-js/mermaid-cli ``` 详细的安装请安装 [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version) diff --git a/docs/README_JA.md b/docs/README_JA.md index dfb9c8372..0a65e83e0 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -60,17 +60,22 @@ ### インストールビデオガイド ### 伝統的なインストール ```bash -# ステップ 1: NPM がシステムにインストールされていることを確認してください。次に mermaid-js をインストールします。(お使いのコンピューターに npm がない場合は、Node.js 公式サイトで Node.js https://nodejs.org/ をインストールしてください。) -npm --version -sudo npm install -g @mermaid-js/mermaid-cli - -# ステップ 2: Python 3.9+ がシステムにインストールされていることを確認してください。これを確認するには: +# ステップ 1: Python 3.9+ がシステムにインストールされていることを確認してください。これを確認するには: python --version -# ステップ 3: リポジトリをローカルマシンにクローンし、インストールする。 +# ステップ 2: リポジトリをローカルマシンにクローンし、インストールする。 git clone https://github.com/geekan/MetaGPT.git cd MetaGPT pip install -e. + +# ステップ 3: startup.py を実行する +# config.yaml を key.yaml にコピーし、独自の OPENAI_API_KEY を設定します +python3 startup.py "Write a cli snake game" + +# ステップ 4 [オプション]: 実行中に PRD ファイルなどのアーティファクトを保存する場合は、ステップ 3 の前にこのステップを実行できます。デフォルトでは、フレームワークには互換性があり、この手順を実行しなくてもプロセス全体を完了できます。 +# NPM がシステムにインストールされていることを確認してください。次に mermaid-js をインストールします。(お使いのコンピューターに npm がない場合は、Node.js 公式サイトで Node.js https://nodejs.org/ をインストールしてください。) +npm --version +sudo npm install -g @mermaid-js/mermaid-cli ``` **注:** From fa6621a6996fdef77301f6ccbec08f4f95453514 Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 15:15:09 +0800 Subject: [PATCH 36/53] rm useless files --- docs/tutorial/faq.md | 6 ------ docs/tutorial/what_can_this_do.md | 0 2 files changed, 6 deletions(-) delete mode 100644 docs/tutorial/faq.md delete mode 100644 docs/tutorial/what_can_this_do.md diff --git a/docs/tutorial/faq.md b/docs/tutorial/faq.md deleted file mode 100644 index 0d2fa7ef0..000000000 --- a/docs/tutorial/faq.md +++ /dev/null @@ -1,6 +0,0 @@ -## FAQs - -### installation - - -### openai usage diff --git a/docs/tutorial/what_can_this_do.md b/docs/tutorial/what_can_this_do.md deleted file mode 100644 index e69de29bb..000000000 From c212c94da35ea80b63749bea536ea7003578a3d3 Mon Sep 17 00:00:00 2001 From: better629 Date: Mon, 20 Nov 2023 15:37:44 +0800 Subject: [PATCH 37/53] fix --- README.md | 2 +- docs/README_CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0606d2be6..a39b509df 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ # Step 3: run the startup.py # setup your OPENAI_API_KEY in key.yaml copy from config.yaml python3 startup.py "Write a cli snake game" -# Step 4 [Optional]: If you want to save the artifacts like PRD in the workspace, you can execute the step before Step 3. By default, the framework is compatible, and the entire process can be run completely without executing this step. +# Step 4 [Optional]: If you want to save the artifacts like diagrams such as quadrant chart, system designs, sequence flow in the workspace, you can execute the step before Step 3. By default, the framework is compatible, and the entire process can be run completely without executing this step. # If executing, ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.) npm --version sudo npm install -g @mermaid-js/mermaid-cli diff --git a/docs/README_CN.md b/docs/README_CN.md index d38d6908b..50cf207b4 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -52,7 +52,7 @@ # 第 3 步:执行startup.py # 拷贝config.yaml为key.yaml,并设置你自己的OPENAI_API_KEY python3 startup.py "Write a cli snake game" -# 第 4 步【可选的】:如果你想在执行过程中保存像PRD文件这些产物,可以在第3步前执行该步骤。默认的,框架做了兼容,在不执行该步的情况下,也可以完整跑完整个流程。 +# 第 4 步【可选的】:如果你想在执行过程中保存像象限图、系统设计、序列流程等图表这些产物,可以在第3步前执行该步骤。默认的,框架做了兼容,在不执行该步的情况下,也可以完整跑完整个流程。 # 如果执行,确保您的系统上安装了 NPM。并使用npm安装mermaid-js npm --version sudo npm install -g @mermaid-js/mermaid-cli From daf29c41afba445bd507d75e6549e0f8fb14ecc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 11:17:33 +0800 Subject: [PATCH 38/53] chore: change name. --- metagpt/provider/constant.py | 4 ++-- metagpt/provider/openai_api.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/metagpt/provider/constant.py b/metagpt/provider/constant.py index f9b84fc60..f228e7c30 100644 --- a/metagpt/provider/constant.py +++ b/metagpt/provider/constant.py @@ -1,6 +1,6 @@ # function in tools, https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools # Reference: https://github.com/KillianLucas/open-interpreter/blob/v0.1.14/interpreter/llm/setup_openai_coding_llm.py -general_function_schema = { +GENRAL_FUNCTION_SCHEMA_4_ASK_CODE = { "name": "execute", "description": "Executes code on the user's machine, **in the users local environment**, and returns the output", "parameters": { @@ -27,4 +27,4 @@ general_function_schema = { # tool_choice value for general_function_schema # https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice -general_tool_choice = {"type": "function", "function": {"name": "execute"}} +GENRAL_TOOL_CHOICE_4_ASK_CODE = {"type": "function", "function": {"name": "execute"}} diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 8f8d45d73..f950c0d67 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -21,7 +21,7 @@ from tenacity import ( from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI -from metagpt.provider.constant import general_function_schema, general_tool_choice +from metagpt.provider.constant import GENRAL_FUNCTION_SCHEMA_4_ASK_CODE, GENRAL_TOOL_CHOICE_4_ASK_CODE from metagpt.schema import Message from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( @@ -245,8 +245,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): def _func_configs(self, messages: list[dict], **kwargs) -> dict: if "tools" not in kwargs: configs = { - "tools": [{"type": "function", "function": general_function_schema}], - "tool_choice": general_tool_choice, + "tools": [{"type": "function", "function": GENRAL_FUNCTION_SCHEMA_4_ASK_CODE}], + "tool_choice": GENRAL_TOOL_CHOICE_4_ASK_CODE, } kwargs.update(configs) From fcc9bd6063b77c758fd10c9d78affde7a93236ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 11:17:56 +0800 Subject: [PATCH 39/53] chore: openai -> openai>=0.28.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 093298775..ff483e97f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ langchain==0.0.231 loguru==0.6.0 meilisearch==0.21.0 numpy==1.24.3 -openai +openai>=0.28.0 openpyxl beautifulsoup4==4.12.2 pandas==2.0.3 From 66f647276e8775478321cd19bddb5f7cce9000d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 11:19:43 +0800 Subject: [PATCH 40/53] Revert "chore: change name." This reverts commit daf29c41afba445bd507d75e6549e0f8fb14ecc5. --- metagpt/provider/constant.py | 4 ++-- metagpt/provider/openai_api.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/metagpt/provider/constant.py b/metagpt/provider/constant.py index f228e7c30..f9b84fc60 100644 --- a/metagpt/provider/constant.py +++ b/metagpt/provider/constant.py @@ -1,6 +1,6 @@ # function in tools, https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools # Reference: https://github.com/KillianLucas/open-interpreter/blob/v0.1.14/interpreter/llm/setup_openai_coding_llm.py -GENRAL_FUNCTION_SCHEMA_4_ASK_CODE = { +general_function_schema = { "name": "execute", "description": "Executes code on the user's machine, **in the users local environment**, and returns the output", "parameters": { @@ -27,4 +27,4 @@ GENRAL_FUNCTION_SCHEMA_4_ASK_CODE = { # tool_choice value for general_function_schema # https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice -GENRAL_TOOL_CHOICE_4_ASK_CODE = {"type": "function", "function": {"name": "execute"}} +general_tool_choice = {"type": "function", "function": {"name": "execute"}} diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index f950c0d67..8f8d45d73 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -21,7 +21,7 @@ from tenacity import ( from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI -from metagpt.provider.constant import GENRAL_FUNCTION_SCHEMA_4_ASK_CODE, GENRAL_TOOL_CHOICE_4_ASK_CODE +from metagpt.provider.constant import general_function_schema, general_tool_choice from metagpt.schema import Message from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( @@ -245,8 +245,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): def _func_configs(self, messages: list[dict], **kwargs) -> dict: if "tools" not in kwargs: configs = { - "tools": [{"type": "function", "function": GENRAL_FUNCTION_SCHEMA_4_ASK_CODE}], - "tool_choice": GENRAL_TOOL_CHOICE_4_ASK_CODE, + "tools": [{"type": "function", "function": general_function_schema}], + "tool_choice": general_tool_choice, } kwargs.update(configs) From 4418fe8283635b5bdf64489b916c073d97695f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 11:40:15 +0800 Subject: [PATCH 41/53] feat: ask_code message support type list[Message]. --- metagpt/provider/openai_api.py | 4 ++-- tests/metagpt/provider/test_openai.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 8f8d45d73..11f429601 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -262,10 +262,10 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): self._update_costs(rsp.get("usage")) return rsp - def _process_message(self, messages: Union[str, Message, list[dict]]) -> list[dict]: + def _process_message(self, messages: Union[str, Message, list[dict], list[Message]]) -> list[dict]: """convert messages to list[dict].""" if isinstance(messages, list): - return messages + return [msg if isinstance(msg, dict) else msg.to_dict() for msg in messages] if isinstance(messages, Message): messages = [messages.to_dict()] diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py index d6d9f4f9d..93ac6623c 100644 --- a/tests/metagpt/provider/test_openai.py +++ b/tests/metagpt/provider/test_openai.py @@ -59,3 +59,12 @@ def test_ask_code_Message(): assert "language" in rsp assert "code" in rsp assert len(rsp["code"]) > 0 + + +def test_ask_code_list_Message(): + llm = OpenAIGPTAPI() + msg = [UserMessage("a=[1,2,5,10,-10]"), UserMessage("写出求a中最大值的代码python")] + rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': 'max_value = max(a)\nmax_value'} + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 From 657571a192bab2dd092ac4bf5860411489dc9e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 12:01:28 +0800 Subject: [PATCH 42/53] feat: ask_code message support type list[str]. --- metagpt/provider/openai_api.py | 3 ++- tests/metagpt/provider/test_openai.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 11f429601..ef9b790ce 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -262,9 +262,10 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): self._update_costs(rsp.get("usage")) return rsp - def _process_message(self, messages: Union[str, Message, list[dict], list[Message]]) -> list[dict]: + def _process_message(self, messages: Union[str, Message, list[dict], list[Message], list[str]]) -> list[dict]: """convert messages to list[dict].""" if isinstance(messages, list): + messages = [Message(msg) if isinstance(msg, str) else msg for msg in messages] return [msg if isinstance(msg, dict) else msg.to_dict() for msg in messages] if isinstance(messages, Message): diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py index 93ac6623c..2b0af37b5 100644 --- a/tests/metagpt/provider/test_openai.py +++ b/tests/metagpt/provider/test_openai.py @@ -68,3 +68,13 @@ def test_ask_code_list_Message(): assert "language" in rsp assert "code" in rsp assert len(rsp["code"]) > 0 + + +def test_ask_code_list_str(): + llm = OpenAIGPTAPI() + msg = ["a=[1,2,5,10,-10]", "写出求a中最大值的代码python"] + rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': 'max_value = max(a)\nmax_value'} + print(rsp) + assert "language" in rsp + assert "code" in rsp + assert len(rsp["code"]) > 0 From 9d1d8a9fe4c7946c3c36995d6584491dfd17f97c Mon Sep 17 00:00:00 2001 From: better629 Date: Tue, 21 Nov 2023 14:10:04 +0800 Subject: [PATCH 43/53] fix --- metagpt/provider/zhipuai_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py index 2ad1944c2..064ec35ba 100644 --- a/metagpt/provider/zhipuai_api.py +++ b/metagpt/provider/zhipuai_api.py @@ -20,7 +20,6 @@ from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.provider.openai_api import CostManager, log_and_reraise from metagpt.provider.zhipuai.zhipu_model_api import ZhiPuModelAPI -from metagpt.utils.ahttp_client import astream class ZhiPuEvent(Enum): From ded2044be7eb244ad0549873b5041945a8da674d Mon Sep 17 00:00:00 2001 From: better629 Date: Tue, 21 Nov 2023 14:50:31 +0800 Subject: [PATCH 44/53] rm useless func --- metagpt/provider/general_api_requestor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/provider/general_api_requestor.py b/metagpt/provider/general_api_requestor.py index 169b7c146..150f2f1e0 100644 --- a/metagpt/provider/general_api_requestor.py +++ b/metagpt/provider/general_api_requestor.py @@ -6,7 +6,7 @@ from typing import AsyncGenerator, Tuple, Union, Optional, Literal import aiohttp import asyncio -from openai.api_requestor import APIRequestor, aiohttp_session +from openai.api_requestor import APIRequestor from metagpt.logs import logger From 45655a2a28efc07fcaacd7d91106eaa1f2d12984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 16:34:12 +0800 Subject: [PATCH 45/53] chore: add function signature to get_choice_function and get_choice_function_arguments. --- metagpt/provider/base_gpt_api.py | 39 +++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/metagpt/provider/base_gpt_api.py b/metagpt/provider/base_gpt_api.py index 2cfc3fa1f..f23ef871c 100644 --- a/metagpt/provider/base_gpt_api.py +++ b/metagpt/provider/base_gpt_api.py @@ -111,16 +111,43 @@ class BaseGPTAPI(BaseChatbot): return rsp.get("choices")[0]["message"]["content"] def get_choice_function(self, rsp: dict) -> dict: - """Required to provide the first function of choice. for example: - "function": { - "name": "execute", - "arguments": "{\n \"language\": \"python\",\n \"code\": \"print('Hello, World!')\"\n}" - } + """Required to provide the first function of choice + :param dict rsp: OpenAI chat.comletion respond JSON, Note "message" must include "tool_calls", + and "tool_calls" must include "function", for example: + {... + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_Y5r6Ddr2Qc2ZrqgfwzPX5l72", + "type": "function", + "function": { + "name": "execute", + "arguments": "{\n \"language\": \"python\",\n \"code\": \"print('Hello, World!')\"\n}" + } + } + ] + }, + "finish_reason": "stop" + } + ], + ...} + :return dict: return first function of choice, for exmaple, + {'name': 'execute', 'arguments': '{\n "language": "python",\n "code": "print(\'Hello, World!\')"\n}'} """ return rsp.get("choices")[0]["message"]["tool_calls"][0]["function"].to_dict() def get_choice_function_arguments(self, rsp: dict) -> dict: - """Required to provide the first function arguments of choice.""" + """Required to provide the first function arguments of choice. + + :param dict rsp: same as in self.get_choice_function(rsp) + :return dict: return the first function arguments of choice, for example, + {'language': 'python', 'code': "print('Hello, World!')"} + """ return json.loads(self.get_choice_function(rsp)["arguments"]) def messages_to_prompt(self, messages: list[dict]): From bec4bc0e8386a9866904ab92cc55df1d155549df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 17:01:03 +0800 Subject: [PATCH 46/53] chore: add comments for `kwargs`. --- metagpt/provider/openai_api.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index ef9b790ce..af7810b62 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -243,6 +243,9 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): return self.get_choice_text(rsp) def _func_configs(self, messages: list[dict], **kwargs) -> dict: + """ + Note: Keep kwargs consistent with the parameters in the https://platform.openai.com/docs/api-reference/chat/create + """ if "tools" not in kwargs: configs = { "tools": [{"type": "function", "function": general_function_schema}], @@ -280,7 +283,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): def ask_code(self, messages: Union[str, Message, list[dict]], **kwargs) -> dict: """Use function of tools to ask a code. - https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools + + Note: Keep kwargs consistent with the parameters in the https://platform.openai.com/docs/api-reference/chat/create Examples: @@ -297,7 +301,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): async def aask_code(self, messages: Union[str, Message, list[dict]], **kwargs) -> dict: """Use function of tools to ask a code. - https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools + + Note: Keep kwargs consistent with the parameters in the https://platform.openai.com/docs/api-reference/chat/create Examples: From 7a5c66b01edcd01ed26426df3ce6b7b2c0cfd733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 21 Nov 2023 17:14:39 +0800 Subject: [PATCH 47/53] chore: general_function_schema, general_tool_choice -> GENERAL_FUNCTION_SCHEMA, GENERAL_TOOL_CHOICE --- metagpt/provider/constant.py | 4 ++-- metagpt/provider/openai_api.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/metagpt/provider/constant.py b/metagpt/provider/constant.py index f9b84fc60..db67847a8 100644 --- a/metagpt/provider/constant.py +++ b/metagpt/provider/constant.py @@ -1,6 +1,6 @@ # function in tools, https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools # Reference: https://github.com/KillianLucas/open-interpreter/blob/v0.1.14/interpreter/llm/setup_openai_coding_llm.py -general_function_schema = { +GENERAL_FUNCTION_SCHEMA = { "name": "execute", "description": "Executes code on the user's machine, **in the users local environment**, and returns the output", "parameters": { @@ -27,4 +27,4 @@ general_function_schema = { # tool_choice value for general_function_schema # https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice -general_tool_choice = {"type": "function", "function": {"name": "execute"}} +GENERAL_TOOL_CHOICE = {"type": "function", "function": {"name": "execute"}} diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index af7810b62..34e5693f8 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -21,7 +21,7 @@ from tenacity import ( from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI -from metagpt.provider.constant import general_function_schema, general_tool_choice +from metagpt.provider.constant import GENERAL_FUNCTION_SCHEMA, GENERAL_TOOL_CHOICE from metagpt.schema import Message from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( @@ -248,8 +248,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): """ if "tools" not in kwargs: configs = { - "tools": [{"type": "function", "function": general_function_schema}], - "tool_choice": general_tool_choice, + "tools": [{"type": "function", "function": GENERAL_FUNCTION_SCHEMA}], + "tool_choice": GENERAL_TOOL_CHOICE, } kwargs.update(configs) From dfe91685098312f3c8eb729d6ebb0c1a411e23b6 Mon Sep 17 00:00:00 2001 From: Sirui Hong <34952977+stellaHSR@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:35:33 +0800 Subject: [PATCH 48/53] Update llm.py rm 'sk-' verification --- metagpt/llm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/llm.py b/metagpt/llm.py index 51ade1f7e..b653df946 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -18,7 +18,7 @@ from metagpt.provider.human_provider import HumanProvider def LLM() -> "BaseGPTAPI": """ initialize different LLM instance according to the key field existence""" # TODO a little trick, can use registry to initialize LLM instance further - if CONFIG.openai_api_key and CONFIG.openai_api_key.startswith("sk-"): + if CONFIG.openai_api_key: llm = OpenAIGPTAPI() elif CONFIG.claude_api_key: llm = Claude() From 322aef6f1d24e86e40718b702a4cb11660061822 Mon Sep 17 00:00:00 2001 From: better629 Date: Tue, 21 Nov 2023 19:24:57 +0800 Subject: [PATCH 49/53] fix zhipuapi's key to init openai api_key when using openai sdk --- metagpt/llm.py | 2 +- metagpt/provider/zhipuai_api.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/metagpt/llm.py b/metagpt/llm.py index b653df946..4edcd7a83 100644 --- a/metagpt/llm.py +++ b/metagpt/llm.py @@ -24,7 +24,7 @@ def LLM() -> "BaseGPTAPI": llm = Claude() elif CONFIG.spark_api_key: llm = SparkAPI() - elif CONFIG.zhipuai_api_key and CONFIG.zhipuai_api_key != "YOUR_API_KEY": + elif CONFIG.zhipuai_api_key: llm = ZhiPuAIGPTAPI() else: raise RuntimeError("You should config a LLM configuration first") diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py index 064ec35ba..3161c0e88 100644 --- a/metagpt/provider/zhipuai_api.py +++ b/metagpt/provider/zhipuai_api.py @@ -13,6 +13,7 @@ from tenacity import ( ) from requests import ConnectionError +import openai import zhipuai from metagpt.config import CONFIG @@ -46,6 +47,7 @@ class ZhiPuAIGPTAPI(BaseGPTAPI): def __init_zhipuai(self, config: CONFIG): assert config.zhipuai_api_key zhipuai.api_key = config.zhipuai_api_key + openai.api_key = zhipuai.api_key # due to use openai sdk, set the api_key but it will't be used. def _const_kwargs(self, messages: list[dict]) -> dict: kwargs = { From 7f1642720509cae1d2ce28d3f9a49c81e889a4ea Mon Sep 17 00:00:00 2001 From: seehi <6580@pm.me> Date: Wed, 22 Nov 2023 11:52:47 +0800 Subject: [PATCH 50/53] update readme --- README.md | 1 + docs/README_CN.md | 1 + docs/README_JA.md | 1 + 3 files changed, 3 insertions(+) diff --git a/README.md b/README.md index ead43c9e7..0ad622576 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ # If executing, ensure that NPM is installed on your system. Then install mermai detail installation please refer to [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version) ### Docker installation +> Note: In the Windows, you need to replace "/opt/metagpt" with a directory that Docker has permission to create, such as "D:\Users\x\metagpt" ```bash # Step 1: Download metagpt official image and prepare config.yaml diff --git a/docs/README_CN.md b/docs/README_CN.md index 409bdc7af..ebdc63022 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -60,6 +60,7 @@ # 如果执行,确保您的系统上安装了 NPM。并使用npm安装mermaid- 详细的安装请安装 [cli_install](https://docs.deepwisdom.ai/guide/get_started/installation.html#install-stable-version) ### Docker安装 +> 注意:在Windows中,你需要将 "/opt/metagpt" 替换为Docker具有创建权限的目录,比如"D:\Users\x\metagpt" ```bash # 步骤1: 下载metagpt官方镜像并准备好config.yaml diff --git a/docs/README_JA.md b/docs/README_JA.md index 10cb7ee82..988dcde7a 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -163,6 +163,7 @@ # NPM がシステムにインストールされていることを確認して 注: この方法は pdf エクスポートに対応していません。 ### Docker によるインストール +> Windowsでは、"/opt/metagpt"をDockerが作成する権限を持つディレクトリに置き換える必要があります。例えば、"D:\Users\x\metagpt"などです。 ```bash # ステップ 1: metagpt 公式イメージをダウンロードし、config.yaml を準備する From 7ef9fddc6413f1ef94fcf1a683c8ae071f55e436 Mon Sep 17 00:00:00 2001 From: better629 Date: Fri, 24 Nov 2023 10:06:42 +0800 Subject: [PATCH 51/53] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ead43c9e7..fc8781014 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ ### Contact Information ## Citation -For now, cite the [Arxiv paper](https://arxiv.org/abs/2308.00352): +For now, cite the [arXiv paper](https://arxiv.org/abs/2308.00352): ```bibtex @misc{hong2023metagpt, From 7a4187001560f9f4db73052f0912abb3a309b527 Mon Sep 17 00:00:00 2001 From: better629 Date: Fri, 24 Nov 2023 10:07:12 +0800 Subject: [PATCH 52/53] Update README_CN.md --- docs/README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README_CN.md b/docs/README_CN.md index 409bdc7af..6721ca9ca 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -120,7 +120,7 @@ ### 联系信息 ## 引用 -引用 [Arxiv paper](https://arxiv.org/abs/2308.00352): +引用 [arXiv paper](https://arxiv.org/abs/2308.00352): ```bibtex @misc{hong2023metagpt, From 9e97e6d3d0da67e220c263f23ec54cdc398bdcc2 Mon Sep 17 00:00:00 2001 From: better629 Date: Fri, 24 Nov 2023 10:07:38 +0800 Subject: [PATCH 53/53] Update README_JA.md --- docs/README_JA.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README_JA.md b/docs/README_JA.md index 10cb7ee82..a38b92f5b 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -299,7 +299,7 @@ ## クイックスタート ## 引用 -現時点では、[Arxiv 論文](https://arxiv.org/abs/2308.00352)を引用してください: +現時点では、[arXiv 論文](https://arxiv.org/abs/2308.00352)を引用してください: ```bibtex @misc{hong2023metagpt,