diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 06d12e408..46788e306 100644 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -4,4 +4,4 @@ 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 -python setup.py install \ No newline at end of file +pip install -e. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6352a90e5..e03eab3d3 100644 --- a/.gitignore +++ b/.gitignore @@ -148,8 +148,7 @@ allure-results .DS_Store .vscode - -*.txt +log.txt docs/scripts/set_env.sh key.yaml output.json @@ -164,3 +163,4 @@ workspace/* tmp output.wav metagpt/roles/idea_agent.py +.aider* diff --git a/Dockerfile b/Dockerfile index 537bbc72e..8ab180e28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY . /app/metagpt WORKDIR /app/metagpt RUN mkdir workspace &&\ pip install --no-cache-dir -r requirements.txt &&\ - python setup.py install + pip install -e. # Running with an infinite loop using the tail command CMD ["sh", "-c", "tail -f /dev/null"] diff --git a/README.md b/README.md index a3f669d7b..91a5483e0 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,17 @@ # MetaGPT: The Multi-Agent Framework CN doc EN doc JA doc -Discord Follow -License: MIT +Discord Follow +License: MIT roadmap Twitter Follow

+ AgentStore Waitlist Open in Dev Containers Open in GitHub Codespaces + Hugging Face

1. MetaGPT takes a **one line requirement** as input and outputs **user stories / competitive analysis / requirements / data structures / APIs / documents, etc.** @@ -31,6 +33,13 @@ # MetaGPT: The Multi-Agent Framework

Software Company Multi-Role Schematic (Gradually Implementing)

+## MetaGPT's Abilities + + +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 @@ -39,6 +48,9 @@ ## Examples (fully generated by GPT-4) 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 @@ -48,7 +60,7 @@ ### Installation Video Guide ### Traditional Installation ```bash -# Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. +# 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 offical 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 @@ -58,7 +70,7 @@ # Step 2: Ensure that Python 3.9+ is installed on your system. You can check thi # Step 3: Clone the repository to your local machine, and install it. git clone https://github.com/geekan/metagpt cd metagpt -python setup.py install +pip install -e. ``` **Note:** @@ -79,7 +91,72 @@ # Step 3: Clone the repository to your local machine, and install it. MMDC: "./node_modules/.bin/mmdc" ``` -- if `python setup.py install` fails with error `[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`, try instead running `python setup.py install --user` +- 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 alow 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 @@ -213,6 +290,9 @@ ## QuickStart - [MetaGPT quickstart](https://deepwisdom.feishu.cn/wiki/CyY9wdJc4iNqArku3Lncl4v8n2b) +Try it on Huggingface Space +- https://huggingface.co/spaces/deepwisdom/MetaGPT + ## Citation For now, cite the [Arxiv paper](https://arxiv.org/abs/2308.00352): diff --git a/config/config.yaml b/config/config.yaml index 4519288d3..93301fcf2 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -76,3 +76,10 @@ SD_T2I_API: "/sdapi/v1/txt2img" ### for Research MODEL_FOR_RESEARCHER_SUMMARY: gpt-3.5-turbo MODEL_FOR_RESEARCHER_REPORT: gpt-3.5-turbo-16k + +### choose the engine for mermaid conversion, +# default is nodejs, you can change it to playwright,pyppeteer or ink +# MERMAID_ENGINE: nodejs + +### browser path for pyppeteer engine, support Chrome, Chromium,MS Edge +#PYPPETEER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable" \ No newline at end of file diff --git a/docs/README_CN.md b/docs/README_CN.md index ae5d954e4..1372bf9f4 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -9,16 +9,22 @@ # MetaGPT: 多智能体框架

-CN doc -EN doc -JA doc -Discord Follow -License: MIT -roadmap -roadmap +CN doc +EN doc +JA doc +Discord Follow +License: MIT +roadmap Twitter Follow

+

+ AgentStore Waitlist + Open in Dev Containers + Open in GitHub Codespaces + Hugging Face +

+ 1. MetaGPT输入**一句话的老板需求**,输出**用户故事 / 竞品分析 / 需求 / 数据结构 / APIs / 文件等** 2. MetaGPT内部包括**产品经理 / 架构师 / 项目经理 / 工程师**,它提供了一个**软件公司**的全过程与精心调配的SOP 1. `Code = SOP(Team)` 是核心哲学。我们将SOP具象化,并且用于LLM构成的团队 @@ -27,6 +33,11 @@ # MetaGPT: 多智能体框架

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

+## MetaGPT 的能力 + +https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419 + + ## 示例(均由 GPT-4 生成) 例如,键入`python startup.py "写个类似今日头条的推荐系统"`并回车,你会获得一系列输出,其一是数据结构与API设计 @@ -50,7 +61,7 @@ # 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下 # 第 3 步:克隆仓库到您的本地机器,并进行安装。 git clone https://github.com/geekan/metagpt cd metagpt -python setup.py install +pip install -e. ``` **注意:** @@ -70,7 +81,7 @@ # 第 3 步:克隆仓库到您的本地机器,并进行安装。 MMDC: "./node_modules/.bin/mmdc" ``` -- 如果`python setup.py install`失败并显示错误`[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`,请尝试使用`python setup.py install --user`运行。 +- 如果`pip install -e.`失败并显示错误`[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`,请尝试使用`pip install -e. --user`运行。 ### Docker安装 @@ -125,10 +136,10 @@ # 复制配置文件并进行必要的修改 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" | +| 变量名 | 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" | ## 示例:启动一个创业公司 @@ -198,6 +209,10 @@ ## 快速体验 - [MetaGPT快速体验](https://deepwisdom.feishu.cn/wiki/Q8ycw6J9tiNXdHk66MRcIN8Pnlg) +可直接在Huggingface Space体验 + +- https://huggingface.co/spaces/deepwisdom/MetaGPT + ## 联系信息 如果您对这个项目有任何问题或反馈,欢迎联系我们。我们非常欢迎您的建议! diff --git a/docs/README_JA.md b/docs/README_JA.md index 4be862b6f..8d6c2fe84 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -9,16 +9,22 @@ # MetaGPT: マルチエージェントフレームワーク

-CN doc -EN doc -JA doc -Discord Follow -License: MIT +CN doc +EN doc +JA doc +Discord Follow +License: MIT roadmap -roadmap Twitter Follow

+

+ AgentStore Waitlist + Open in Dev Containers + Open in GitHub Codespaces + Hugging Face +

+ 1. MetaGPT は、**1 行の要件** を入力とし、**ユーザーストーリー / 競合分析 / 要件 / データ構造 / API / 文書など** を出力します。 2. MetaGPT には、**プロダクト マネージャー、アーキテクト、プロジェクト マネージャー、エンジニア** が含まれています。MetaGPT は、**ソフトウェア会社のプロセス全体を、慎重に調整された SOP とともに提供します。** 1. `Code = SOP(Team)` が基本理念です。私たちは SOP を具体化し、LLM で構成されるチームに適用します。 @@ -27,6 +33,11 @@ # MetaGPT: マルチエージェントフレームワーク

ソフトウェア会社のマルチロール図式(順次導入)

+## MetaGPTの能力 + +https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419 + + ## 例(GPT-4 で完全生成) 例えば、`python startup.py "Toutiao のような RecSys をデザインする"`と入力すると、多くの出力が得られます @@ -37,6 +48,10 @@ ## 例(GPT-4 で完全生成) ## インストール +### インストールビデオガイド + +- [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) + ### 伝統的なインストール ```bash @@ -50,7 +65,7 @@ # ステップ 2: Python 3.9+ がシステムにインストールされてい # ステップ 3: リポジトリをローカルマシンにクローンし、インストールする。 git clone https://github.com/geekan/metagpt cd metagpt -python setup.py install +pip install -e. ``` **注:** @@ -60,18 +75,18 @@ # ステップ 3: リポジトリをローカルマシンにクローンし、 - このツールをグローバルにインストールする[問題を抱えている](https://github.com/mermaidjs/mermaid.cli/issues/15)人もいます。ローカルにインストールするのが代替の解決策です、 - ```bash - npm install @mermaid-js/mermaid-cli - ``` + ```bash + npm install @mermaid-js/mermaid-cli + ``` - config.yml に mmdc のコンフィギュレーションを記述するのを忘れないこと - ```yml - PUPPETEER_CONFIG: "./config/puppeteer-config.json" - MMDC: "./node_modules/.bin/mmdc" - ``` + ```yml + PUPPETEER_CONFIG: "./config/puppeteer-config.json" + MMDC: "./node_modules/.bin/mmdc" + ``` -- もし `python setup.py install` がエラー `[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'` で失敗したら、代わりに `python setup.py install --user` を実行してみてください +- もし `pip install -e.` がエラー `[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'` で失敗したら、代わりに `pip install -e. --user` を実行してみてください ### Docker によるインストール @@ -126,26 +141,32 @@ # 設定ファイルをコピーし、必要な修正を加える。 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" | +| 変数名 | 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 "Write a cli snake game" -# コードレビューを利用すれば、コストはかかるが、より良いコード品質を選ぶことができます。 +# プロジェクトの実施にエンジニアを雇わないこと +python startup.py "Write a cli snake game" --implement False +# エンジニアを雇い、コードレビューを行う python startup.py "Write a cli snake game" --code_review True ``` スクリプトを実行すると、`workspace/` ディレクトリに新しいプロジェクトが見つかります。 + ### プラットフォームまたはツールの設定 要件を述べるときに、どのプラットフォームまたはツールを使用するかを指定できます。 + ```shell python startup.py "pygame をベースとした cli ヘビゲームを書く" ``` + ### 使用方法 ``` @@ -194,13 +215,18 @@ ### コードウォークスルー `examples` でシングル・ロール(ナレッジ・ベース付き)と LLM のみの例を詳しく見ることができます。 ## クイックスタート + ローカル環境のインストールや設定は、ユーザーによっては難しいものです。以下のチュートリアルで MetaGPT の魅力をすぐに体験できます。 - [MetaGPT クイックスタート](https://deepwisdom.feishu.cn/wiki/CyY9wdJc4iNqArku3Lncl4v8n2b) +Hugging Face Space で試す +- https://huggingface.co/spaces/deepwisdom/MetaGPT + ## 引用 現時点では、[Arxiv 論文](https://arxiv.org/abs/2308.00352)を引用してください: + ```bibtex @misc{hong2023metagpt, title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework}, @@ -224,3 +250,10 @@ ## お問い合わせ先 ## デモ https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d + +## 参加する + +📢 Discord チャンネルに参加してください! +https://discord.gg/ZRHeExS6xv + +お会いできることを楽しみにしています! 🎉 diff --git a/docs/resources/20230811-214014.jpg b/docs/resources/20230811-214014.jpg deleted file mode 100644 index 2006f2646..000000000 Binary files a/docs/resources/20230811-214014.jpg and /dev/null differ diff --git a/docs/resources/MetaGPT-WeChat-Personal.jpeg b/docs/resources/MetaGPT-WeChat-Personal.jpeg deleted file mode 100644 index f6b48577d..000000000 Binary files a/docs/resources/MetaGPT-WeChat-Personal.jpeg and /dev/null differ diff --git a/docs/resources/MetaGPT-WorkWeChatGroup-6.jpg b/docs/resources/MetaGPT-WorkWeChatGroup-6.jpg deleted file mode 100644 index 77a4668f7..000000000 Binary files a/docs/resources/MetaGPT-WorkWeChatGroup-6.jpg and /dev/null differ diff --git a/examples/sk_agent.py b/examples/sk_agent.py new file mode 100644 index 000000000..f60e7299b --- /dev/null +++ b/examples/sk_agent.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/13 12:36 +@Author : femto Zheng +@File : sk_agent.py +""" +import asyncio + +from semantic_kernel.core_skills import FileIOSkill, MathSkill, TextSkill, TimeSkill +from semantic_kernel.planning import SequentialPlanner + +# from semantic_kernel.planning import SequentialPlanner +from semantic_kernel.planning.action_planner.action_planner import ActionPlanner + +from metagpt.actions import BossRequirement +from metagpt.const import SKILL_DIRECTORY +from metagpt.roles.sk_agent import SkAgent +from metagpt.schema import Message +from metagpt.tools.search_engine import SkSearchEngine + + +async def main(): + await basic_planner_example() + await action_planner_example() + + # await sequential_planner_example() + # await basic_planner_web_search_example() + + +async def basic_planner_example(): + task = """ + Tomorrow is Valentine's day. I need to come up with a few date ideas. She speaks French so write it in French. + Convert the text to uppercase""" + role = SkAgent() + + # let's give the agent some skills + role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "SummarizeSkill") + role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "WriterSkill") + role.import_skill(TextSkill(), "TextSkill") + # using BasicPlanner + await role.run(Message(content=task, cause_by=BossRequirement)) + + +async def sequential_planner_example(): + task = """ + Tomorrow is Valentine's day. I need to come up with a few date ideas. She speaks French so write it in French. + Convert the text to uppercase""" + role = SkAgent(planner_cls=SequentialPlanner) + + # let's give the agent some skills + role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "SummarizeSkill") + role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "WriterSkill") + role.import_skill(TextSkill(), "TextSkill") + # using BasicPlanner + await role.run(Message(content=task, cause_by=BossRequirement)) + + +async def basic_planner_web_search_example(): + task = """ + Question: Who made the 1989 comic book, the film version of which Jon Raymond Polito appeared in?""" + role = SkAgent() + + role.import_skill(SkSearchEngine(), "WebSearchSkill") + # role.import_semantic_skill_from_directory(skills_directory, "QASkill") + + await role.run(Message(content=task, cause_by=BossRequirement)) + + +async def action_planner_example(): + role = SkAgent(planner_cls=ActionPlanner) + # let's give the agent 4 skills + role.import_skill(MathSkill(), "math") + role.import_skill(FileIOSkill(), "fileIO") + role.import_skill(TimeSkill(), "time") + role.import_skill(TextSkill(), "text") + task = "What is the sum of 110 and 990?" + await role.run(Message(content=task, cause_by=BossRequirement)) # it will choose mathskill.Add + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/write_tutorial.py b/examples/write_tutorial.py new file mode 100644 index 000000000..167f3eb7c --- /dev/null +++ b/examples/write_tutorial.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/4 21:40:57 +@Author : Stitch-z +@File : tutorial_assistant.py +""" +import asyncio + +from metagpt.roles.tutorial_assistant import TutorialAssistant + + +async def main(): + topic = "Write a tutorial about MySQL" + role = TutorialAssistant(language="Chinese") + await role.run(topic) + + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/metagpt/__init__.py b/metagpt/__init__.py index b9c530d24..71ddd1aff 100644 --- a/metagpt/__init__.py +++ b/metagpt/__init__.py @@ -1,5 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2023/4/24 22:26 # @Author : alexanderwu # @File : __init__.py + +from metagpt import _compat as _ # noqa: F401 diff --git a/metagpt/_compat.py b/metagpt/_compat.py new file mode 100644 index 000000000..e94a4d095 --- /dev/null +++ b/metagpt/_compat.py @@ -0,0 +1,15 @@ +import platform +import sys +import warnings + +if sys.implementation.name == "cpython" and platform.system() == "Windows" and sys.version_info[:2] == (3, 9): + # https://github.com/python/cpython/pull/92842 + + from asyncio.proactor_events import _ProactorBasePipeTransport + + def pacth_del(self, _warn=warnings.warn): + if self._sock is not None: + _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) + self._sock.close() + + _ProactorBasePipeTransport.__del__ = pacth_del diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index abd1f9d4c..4d17e4f5e 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -103,23 +103,23 @@ class WriteDesign(Action): pass # Folder does not exist, but we don't care workspace.mkdir(parents=True, exist_ok=True) - def _save_prd(self, docs_path, resources_path, prd): + async def _save_prd(self, docs_path, resources_path, prd): prd_file = docs_path / 'prd.md' quadrant_chart = CodeParser.parse_code(block="Competitive Quadrant Chart", text=prd) - mermaid_to_file(quadrant_chart, resources_path / 'competitive_analysis') + await mermaid_to_file(quadrant_chart, resources_path / 'competitive_analysis') logger.info(f"Saving PRD to {prd_file}") prd_file.write_text(prd) - def _save_system_design(self, docs_path, resources_path, content): + async def _save_system_design(self, docs_path, resources_path, content): data_api_design = CodeParser.parse_code(block="Data structures and interface definitions", text=content) seq_flow = CodeParser.parse_code(block="Program call flow", text=content) - mermaid_to_file(data_api_design, resources_path / 'data_api_design') - mermaid_to_file(seq_flow, resources_path / 'seq_flow') + await mermaid_to_file(data_api_design, resources_path / 'data_api_design') + await mermaid_to_file(seq_flow, resources_path / 'seq_flow') system_design_file = docs_path / 'system_design.md' logger.info(f"Saving System Designs to {system_design_file}") system_design_file.write_text(content) - def _save(self, context, system_design): + async def _save(self, context, system_design): if isinstance(system_design, ActionOutput): content = system_design.content ws_name = CodeParser.parse_str(block="Python package name", text=content) @@ -132,13 +132,13 @@ class WriteDesign(Action): resources_path = workspace / 'resources' docs_path.mkdir(parents=True, exist_ok=True) resources_path.mkdir(parents=True, exist_ok=True) - self._save_prd(docs_path, resources_path, context[-1].content) - self._save_system_design(docs_path, resources_path, content) + await self._save_prd(docs_path, resources_path, context[-1].content) + await self._save_system_design(docs_path, resources_path, content) async def run(self, context): prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) # system_design = await self._aask(prompt) system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING) - self._save(context, system_design) + await self._save(context, system_design) return system_design \ No newline at end of file diff --git a/metagpt/actions/execute_task.py b/metagpt/actions/execute_task.py new file mode 100644 index 000000000..afdeda323 --- /dev/null +++ b/metagpt/actions/execute_task.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/13 12:26 +@Author : femto Zheng +@File : execute_task.py +""" +from metagpt.actions import Action +from metagpt.schema import Message + + +class ExecuteTask(Action): + def __init__(self, name="ExecuteTask", context: list[Message] = None, llm=None): + super().__init__(name, context, llm) + + def run(self, *args, **kwargs): + pass diff --git a/metagpt/actions/prepare_interview.py b/metagpt/actions/prepare_interview.py new file mode 100644 index 000000000..5db3a9f37 --- /dev/null +++ b/metagpt/actions/prepare_interview.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/19 15:02 +@Author : DevXiaolan +@File : prepare_interview.py +""" +from metagpt.actions import Action + +PROMPT_TEMPLATE = """ +# Context +{context} + +## Format example +--- +Q1: question 1 here +References: + - point 1 + - point 2 + +Q2: question 2 here... +--- + +----- +Role: You are an interviewer of our company who is well-knonwn in frontend or backend develop; +Requirement: Provide a list of questions for the interviewer to ask the interviewee, by reading the resume of the interviewee in the context. +Attention: Provide as markdown block as the format above, at least 10 questions. +""" + +# prepare for a interview + + +class PrepareInterview(Action): + def __init__(self, name, context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, context): + prompt = PROMPT_TEMPLATE.format(context=context) + question_list = await self._aask_v1(prompt) + return question_list + diff --git a/metagpt/actions/write_tutorial.py b/metagpt/actions/write_tutorial.py new file mode 100644 index 000000000..b23fc2ad4 --- /dev/null +++ b/metagpt/actions/write_tutorial.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/4 15:40:40 +@Author : Stitch-z +@File : tutorial_assistant.py +@Describe : Actions of the tutorial assistant, including writing directories and document content. +""" +import json +from typing import Dict + +from metagpt.actions import Action +from metagpt.logs import logger +from metagpt.prompts.tutorial_assistant import DIRECTORY_PROMPT, CONTENT_PROMPT + + +class WriteDirectory(Action): + """Action class for writing tutorial directories. + + Args: + name: The name of the action. + language: The language to output, default is "Chinese". + """ + + def __init__(self, name: str = "", language: str = "Chinese", *args, **kwargs): + super().__init__(name, *args, **kwargs) + self.language = language + + @staticmethod + async def _handle_resp(resp: str) -> Dict: + """Process string results and convert them to JSON format. + + Args: + resp: The directory results returned by gpt. + + Returns: + The parsed dictionary, such as {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}. + + Raises: + Exception: If no matching dictionary section is found. + json.JSONDecodeError: If the dictionary part cannot be parsed as JSON. + """ + start = resp.find('{') + end = resp.rfind('}') + if start != -1 and end != -1 and end > start: + directory_str = resp[start:end + 1] + logger.info(f"Successfully parsed json: {str(directory_str)}") + try: + return json.loads(directory_str) + except json.JSONDecodeError as e: + logger.error(f"Json parsing error: {e}") + raise e + else: + raise Exception("No matching dictionary section found.") + + async def run(self, topic: str, *args, **kwargs) -> Dict: + """Execute the action to generate a tutorial directory according to the topic. + + Args: + topic: The tutorial topic. + + Returns: + the tutorial directory information, including {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]}. + """ + prompt = DIRECTORY_PROMPT.format(topic=topic, language=self.language) + resp = await self._aask(prompt=prompt) + return await self._handle_resp(resp) + + +class WriteContent(Action): + """Action class for writing tutorial content. + + Args: + name: The name of the action. + directory: The content to write. + language: The language to output, default is "Chinese". + """ + + def __init__(self, name: str = "", directory: str = "", language: str = "Chinese", *args, **kwargs): + super().__init__(name, *args, **kwargs) + self.language = language + self.directory = directory + + async def run(self, topic: str, *args, **kwargs) -> str: + """Execute the action to write document content according to the directory and topic. + + Args: + topic: The tutorial topic. + + Returns: + The written tutorial content. + """ + prompt = CONTENT_PROMPT.format(topic=topic, language=self.language, directory=self.directory) + return await self._aask(prompt=prompt) + diff --git a/metagpt/config.py b/metagpt/config.py index 96f402b38..b4e0fe7fa 100644 --- a/metagpt/config.py +++ b/metagpt/config.py @@ -83,6 +83,8 @@ class Config(metaclass=Singleton): self.calc_usage = self._get("CALC_USAGE", True) self.model_for_researcher_summary = self._get("MODEL_FOR_RESEARCHER_SUMMARY") self.model_for_researcher_report = self._get("MODEL_FOR_RESEARCHER_REPORT") + self.mermaid_engine = self._get("MERMAID_ENGINE", 'nodejs') + self.pyppeteer_executable_path = self._get("PYPPETEER_EXECUTABLE_PATH", '') def _init_with_config_files_and_env(self, configs: dict, yaml_file): """Load from config/key.yaml, config/config.yaml, and env in decreasing order of priority""" diff --git a/metagpt/const.py b/metagpt/const.py index 16f652186..b8b08628e 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -12,9 +12,11 @@ def get_project_root(): """Search upwards to find the project root directory.""" current_path = Path.cwd() while True: - if (current_path / '.git').exists() or \ - (current_path / '.project_root').exists() or \ - (current_path / '.gitignore').exists(): + if ( + (current_path / ".git").exists() + or (current_path / ".project_root").exists() + or (current_path / ".gitignore").exists() + ): return current_path parent_path = current_path.parent if parent_path == current_path: @@ -23,15 +25,18 @@ def get_project_root(): PROJECT_ROOT = get_project_root() -DATA_PATH = PROJECT_ROOT / 'data' -WORKSPACE_ROOT = PROJECT_ROOT / 'workspace' -PROMPT_PATH = PROJECT_ROOT / 'metagpt/prompts' -UT_PATH = PROJECT_ROOT / 'data/ut' +DATA_PATH = PROJECT_ROOT / "data" +WORKSPACE_ROOT = PROJECT_ROOT / "workspace" +PROMPT_PATH = PROJECT_ROOT / "metagpt/prompts" +UT_PATH = PROJECT_ROOT / "data/ut" SWAGGER_PATH = UT_PATH / "files/api/" UT_PY_PATH = UT_PATH / "files/ut/" API_QUESTIONS_PATH = UT_PATH / "files/question/" YAPI_URL = "http://yapi.deepwisdomai.com/" -TMP = PROJECT_ROOT / 'tmp' +TMP = PROJECT_ROOT / "tmp" RESEARCH_PATH = DATA_PATH / "research" +TUTORIAL_PATH = DATA_PATH / "tutorial_docx" + +SKILL_DIRECTORY = PROJECT_ROOT / "metagpt/skills" MEM_TTL = 24 * 30 * 3600 diff --git a/metagpt/prompts/tutorial_assistant.py b/metagpt/prompts/tutorial_assistant.py new file mode 100644 index 000000000..d690aad83 --- /dev/null +++ b/metagpt/prompts/tutorial_assistant.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/4 15:40:40 +@Author : Stitch-z +@File : tutorial_assistant.py +@Describe : Tutorial Assistant's prompt templates. +""" + +COMMON_PROMPT = """ +You are now a seasoned technical professional in the field of the internet. +We need you to write a technical tutorial with the topic "{topic}". +""" + +DIRECTORY_PROMPT = COMMON_PROMPT + """ +Please provide the specific table of contents for this tutorial, strictly following the following requirements: +1. The output must be strictly in the specified language, {language}. +2. Answer strictly in the dictionary format like {{"title": "xxx", "directory": [{{"dir 1": ["sub dir 1", "sub dir 2"]}}, {{"dir 2": ["sub dir 3", "sub dir 4"]}}]}}. +3. The directory should be as specific and sufficient as possible, with a primary and secondary directory.The secondary directory is in the array. +4. Do not have extra spaces or line breaks. +5. Each directory title has practical significance. +""" + +CONTENT_PROMPT = COMMON_PROMPT + """ +Now I will give you the module directory titles for the topic. +Please output the detailed principle content of this title in detail. +If there are code examples, please provide them according to standard code specifications. +Without a code example, it is not necessary. + +The module directory titles for the topic is as follows: +{directory} + +Strictly limit output according to the following requirements: +1. Follow the Markdown syntax format for layout. +2. If there are code examples, they must follow standard syntax specifications, have document annotations, and be displayed in code blocks. +3. The output must be strictly in the specified language, {language}. +4. Do not have redundant output, including concluding remarks. +5. Strict requirement not to output the topic "{topic}". +""" \ No newline at end of file diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index ad9df0396..7e865f288 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -6,11 +6,17 @@ """ import asyncio import time -from typing import NamedTuple +from typing import NamedTuple, Union import openai from openai.error import APIConnectionError -from tenacity import retry, stop_after_attempt, after_log, wait_fixed, retry_if_exception_type +from tenacity import ( + after_log, + retry, + retry_if_exception_type, + stop_after_attempt, + wait_fixed, +) from metagpt.config import CONFIG from metagpt.logs import logger @@ -48,12 +54,14 @@ class RateLimiter: self.last_call_time = time.time() + class Costs(NamedTuple): total_prompt_tokens: int total_completion_tokens: int total_cost: float total_budget: float + class CostManager(metaclass=Singleton): """计算使用接口的开销""" @@ -74,7 +82,9 @@ class CostManager(metaclass=Singleton): """ self.total_prompt_tokens += prompt_tokens self.total_completion_tokens += completion_tokens - cost = (prompt_tokens * TOKEN_COSTS[model]["prompt"] + completion_tokens * TOKEN_COSTS[model]["completion"]) / 1000 + cost = ( + prompt_tokens * TOKEN_COSTS[model]["prompt"] + completion_tokens * TOKEN_COSTS[model]["completion"] + ) / 1000 self.total_cost += cost logger.info( f"Total running cost: ${self.total_cost:.3f} | Max budget: ${CONFIG.max_budget:.3f} | " @@ -100,6 +110,7 @@ class CostManager(metaclass=Singleton): """ return self.total_completion_tokens + def get_total_cost(self): """ Get the total cost of API calls. @@ -109,25 +120,20 @@ def get_total_cost(self): """ 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) -def log_and_reraise(retry_state): - logger.error(f"Retry attempts exhausted. Last exception: {retry_state.outcome.exception()}") - logger.warning(""" -Recommend going to https://deepwisdom.feishu.cn/wiki/MsGnwQBjiif9c3koSJNcYaoSnu4#part-XdatdVlhEojeAfxaaEZcMV3ZniQ -See FAQ 5.8 -""") - raise retry_state.outcome.exception() - def log_and_reraise(retry_state): logger.error(f"Retry attempts exhausted. Last exception: {retry_state.outcome.exception()}") - logger.warning(""" + logger.warning( + """ Recommend going to https://deepwisdom.feishu.cn/wiki/MsGnwQBjiif9c3koSJNcYaoSnu4#part-XdatdVlhEojeAfxaaEZcMV3ZniQ See FAQ 5.8 -""") +""" + ) raise retry_state.outcome.exception() @@ -182,15 +188,18 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): "n": 1, "stop": None, "temperature": 0.3, - "timeout": 3 + "timeout": 3, } 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") elif not CONFIG.deployment_name and not CONFIG.deployment_id: raise ValueError("You must specify `DEPLOYMENT_NAME` or `DEPLOYMENT_ID` parameter") - kwargs_mode = {"engine": CONFIG.deployment_name} if CONFIG.deployment_name \ + kwargs_mode = ( + {"engine": CONFIG.deployment_name} + if CONFIG.deployment_name else {"deployment_id": CONFIG.deployment_id} + ) else: kwargs_mode = {"model": self.model} kwargs.update(kwargs_mode) @@ -219,7 +228,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): @retry( stop=stop_after_attempt(3), wait=wait_fixed(1), - after=after_log(logger, logger.level('WARNING').name), + after=after_log(logger, logger.level("WARNING").name), retry=retry_if_exception_type(APIConnectionError), retry_error_callback=log_and_reraise, ) @@ -236,8 +245,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): try: prompt_tokens = count_message_tokens(messages, self.model) completion_tokens = count_string_tokens(rsp, self.model) - usage['prompt_tokens'] = prompt_tokens - usage['completion_tokens'] = completion_tokens + usage["prompt_tokens"] = prompt_tokens + usage["completion_tokens"] = completion_tokens return usage except Exception as e: logger.error("usage calculation failed!", e) @@ -273,8 +282,8 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): def _update_costs(self, usage: dict): if CONFIG.calc_usage: try: - prompt_tokens = int(usage['prompt_tokens']) - completion_tokens = int(usage['completion_tokens']) + prompt_tokens = int(usage["prompt_tokens"]) + completion_tokens = int(usage["completion_tokens"]) self._cost_manager.update_cost(prompt_tokens, completion_tokens, self.model) except Exception as e: logger.error("updating costs failed!", e) @@ -286,3 +295,31 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): if not self.auto_max_tokens: return CONFIG.max_tokens_rsp return get_max_completion_tokens(messages, self.model, CONFIG.max_tokens_rsp) + + def moderation(self, content: Union[str, list[str]]): + try: + if not content: + logger.error("content cannot be empty!") + else: + rsp = self._moderation(content=content) + return rsp + except Exception as e: + logger.error(f"moderating failed:{e}") + + def _moderation(self, content: Union[str, list[str]]): + rsp = self.llm.Moderation.create(input=content) + return rsp + + async def amoderation(self, content: Union[str, list[str]]): + try: + if not content: + logger.error("content cannot be empty!") + else: + rsp = await self._amoderation(content=content) + return rsp + except Exception as e: + logger.error(f"moderating failed:{e}") + + async def _amoderation(self, content: Union[str, list[str]]): + rsp = await self.llm.Moderation.acreate(input=content) + return rsp diff --git a/metagpt/roles/sk_agent.py b/metagpt/roles/sk_agent.py new file mode 100644 index 000000000..b27841d74 --- /dev/null +++ b/metagpt/roles/sk_agent.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/13 12:23 +@Author : femto Zheng +@File : sk_agent.py +""" +from semantic_kernel.planning import SequentialPlanner +from semantic_kernel.planning.action_planner.action_planner import ActionPlanner +from semantic_kernel.planning.basic_planner import BasicPlanner + +from metagpt.actions import BossRequirement +from metagpt.actions.execute_task import ExecuteTask +from metagpt.logs import logger +from metagpt.roles import Role +from metagpt.schema import Message +from metagpt.utils.make_sk_kernel import make_sk_kernel + + +class SkAgent(Role): + """ + Represents an SkAgent implemented using semantic kernel + + Attributes: + name (str): Name of the SkAgent. + profile (str): Role profile, default is 'sk_agent'. + goal (str): Goal of the SkAgent. + constraints (str): Constraints for the SkAgent. + """ + + def __init__( + self, + name: str = "Sunshine", + profile: str = "sk_agent", + goal: str = "Execute task based on passed in task description", + constraints: str = "", + planner_cls=BasicPlanner, + ) -> None: + """Initializes the Engineer role with given attributes.""" + super().__init__(name, profile, goal, constraints) + self._init_actions([ExecuteTask()]) + self._watch([BossRequirement]) + self.kernel = make_sk_kernel() + + # how funny the interface is inconsistent + if planner_cls == BasicPlanner: + self.planner = planner_cls() + elif planner_cls in [SequentialPlanner, ActionPlanner]: + self.planner = planner_cls(self.kernel) + else: + raise f"Unsupported planner of type {planner_cls}" + + self.import_semantic_skill_from_directory = self.kernel.import_semantic_skill_from_directory + self.import_skill = self.kernel.import_skill + + async def _think(self) -> None: + self._set_state(0) + # how funny the interface is inconsistent + if isinstance(self.planner, BasicPlanner): + self.plan = await self.planner.create_plan_async(self._rc.important_memory[-1].content, self.kernel) + logger.info(self.plan.generated_plan) + elif any(isinstance(self.planner, cls) for cls in [SequentialPlanner, ActionPlanner]): + self.plan = await self.planner.create_plan_async(self._rc.important_memory[-1].content) + + async def _act(self) -> Message: + # how funny the interface is inconsistent + if isinstance(self.planner, BasicPlanner): + result = await self.planner.execute_plan_async(self.plan, self.kernel) + elif any(isinstance(self.planner, cls) for cls in [SequentialPlanner, ActionPlanner]): + result = (await self.plan.invoke_async()).result + logger.info(result) + + msg = Message(content=result, role=self.profile, cause_by=type(self._rc.todo)) + self._rc.memory.add(msg) + # logger.debug(f"{response}") + return msg diff --git a/metagpt/roles/tutorial_assistant.py b/metagpt/roles/tutorial_assistant.py new file mode 100644 index 000000000..9a7df4f4d --- /dev/null +++ b/metagpt/roles/tutorial_assistant.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/4 15:40:40 +@Author : Stitch-z +@File : tutorial_assistant.py +""" + +from datetime import datetime +from typing import Dict + +from metagpt.actions.write_tutorial import WriteDirectory, WriteContent +from metagpt.const import TUTORIAL_PATH +from metagpt.logs import logger +from metagpt.roles import Role +from metagpt.schema import Message +from metagpt.utils.file import File + + +class TutorialAssistant(Role): + """Tutorial assistant, input one sentence to generate a tutorial document in markup format. + + Args: + name: The name of the role. + profile: The role profile description. + goal: The goal of the role. + constraints: Constraints or requirements for the role. + language: The language in which the tutorial documents will be generated. + """ + + def __init__( + self, + name: str = "Stitch", + profile: str = "Tutorial Assistant", + goal: str = "Generate tutorial documents", + constraints: str = "Strictly follow Markdown's syntax, with neat and standardized layout", + language: str = "Chinese", + ): + super().__init__(name, profile, goal, constraints) + self._init_actions([WriteDirectory(language=language)]) + self.topic = "" + self.main_title = "" + self.total_content = "" + self.language = language + + 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 + + async def _handle_directory(self, titles: Dict) -> Message: + """Handle the directories for the tutorial document. + + Args: + titles: A dictionary containing the titles and directory structure, + such as {"title": "xxx", "directory": [{"dir 1": ["sub dir 1", "sub dir 2"]}]} + + Returns: + A message containing information about the directory. + """ + self.main_title = titles.get("title") + directory = f"{self.main_title}\n" + self.total_content += f"# {self.main_title}" + actions = list() + for first_dir in titles.get("directory"): + actions.append(WriteContent(language=self.language, directory=first_dir)) + key = list(first_dir.keys())[0] + directory += f"- {key}\n" + for second_dir in first_dir[key]: + directory += f" - {second_dir}\n" + self._init_actions(actions) + self._rc.todo = None + return Message(content=directory) + + async def _act(self) -> Message: + """Perform an action as determined by the role. + + Returns: + A message containing the result of the action. + """ + todo = self._rc.todo + if type(todo) is WriteDirectory: + msg = self._rc.memory.get(k=1)[0] + self.topic = msg.content + resp = await todo.run(topic=self.topic) + logger.info(resp) + return await self._handle_directory(resp) + resp = await todo.run(topic=self.topic) + logger.info(resp) + if self.total_content != "": + self.total_content += "\n\n\n" + self.total_content += resp + return Message(content=resp, role=self.profile) + + async def _react(self) -> Message: + """Execute the 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() + root_path = TUTORIAL_PATH / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + await File.write(root_path, f"{self.main_title}.md", self.total_content.encode('utf-8')) + return msg diff --git a/metagpt/skills/SummarizeSkill/MakeAbstractReadable/config.json b/metagpt/skills/SummarizeSkill/MakeAbstractReadable/config.json new file mode 100644 index 000000000..0bd48b77a --- /dev/null +++ b/metagpt/skills/SummarizeSkill/MakeAbstractReadable/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Given a scientific white paper abstract, rewrite it to make it more readable", + "completion": { + "max_tokens": 4000, + "temperature": 0.0, + "top_p": 1.0, + "presence_penalty": 0.0, + "frequency_penalty": 2.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/SummarizeSkill/MakeAbstractReadable/skprompt.txt b/metagpt/skills/SummarizeSkill/MakeAbstractReadable/skprompt.txt new file mode 100644 index 000000000..5501e19b7 --- /dev/null +++ b/metagpt/skills/SummarizeSkill/MakeAbstractReadable/skprompt.txt @@ -0,0 +1,5 @@ +{{$input}} + +== +Summarize, using a user friendly, using simple grammar. Don't use subjects like "we" "our" "us" "your". +== \ No newline at end of file diff --git a/metagpt/skills/SummarizeSkill/Notegen/config.json b/metagpt/skills/SummarizeSkill/Notegen/config.json new file mode 100644 index 000000000..f7e1c355e --- /dev/null +++ b/metagpt/skills/SummarizeSkill/Notegen/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Automatically generate compact notes for any text or text document.", + "completion": { + "max_tokens": 256, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/SummarizeSkill/Notegen/skprompt.txt b/metagpt/skills/SummarizeSkill/Notegen/skprompt.txt new file mode 100644 index 000000000..b3f4d203b --- /dev/null +++ b/metagpt/skills/SummarizeSkill/Notegen/skprompt.txt @@ -0,0 +1,21 @@ +Analyze the following extract taken from a document. +- Produce key points for memory. +- Give memory a name. +- Extract only points worth remembering. +- Be brief. Conciseness is very important. +- Use broken English. +You will use this memory to analyze the rest of this document, and for other relevant tasks. + +[Input] +My name is Macbeth. I used to be King of Scotland, but I died. My wife's name is Lady Macbeth and we were married for 15 years. We had no children. Our beloved dog Toby McDuff was a famous hunter of rats in the forest. +My story was immortalized by Shakespeare in a play. ++++++ +Family History +- Macbeth, King Scotland +- Wife Lady Macbeth, No Kids +- Dog Toby McDuff. Hunter, dead. +- Shakespeare play + +[Input] +[[{{$input}}]] ++++++ diff --git a/metagpt/skills/SummarizeSkill/Summarize/config.json b/metagpt/skills/SummarizeSkill/Summarize/config.json new file mode 100644 index 000000000..7ba5cf02d --- /dev/null +++ b/metagpt/skills/SummarizeSkill/Summarize/config.json @@ -0,0 +1,21 @@ +{ + "schema": 1, + "type": "completion", + "description": "Summarize given text or any text document", + "completion": { + "max_tokens": 512, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + }, + "input": { + "parameters": [ + { + "name": "input", + "description": "Text to summarize", + "defaultValue": "" + } + ] + } +} diff --git a/metagpt/skills/SummarizeSkill/Summarize/skprompt.txt b/metagpt/skills/SummarizeSkill/Summarize/skprompt.txt new file mode 100644 index 000000000..5597e1350 --- /dev/null +++ b/metagpt/skills/SummarizeSkill/Summarize/skprompt.txt @@ -0,0 +1,23 @@ +[SUMMARIZATION RULES] +DONT WASTE WORDS +USE SHORT, CLEAR, COMPLETE SENTENCES. +DO NOT USE BULLET POINTS OR DASHES. +USE ACTIVE VOICE. +MAXIMIZE DETAIL, MEANING +FOCUS ON THE CONTENT + +[BANNED PHRASES] +This article +This document +This page +This material +[END LIST] + +Summarize: +Hello how are you? ++++++ +Hello + +Summarize this +{{$input}} ++++++ \ No newline at end of file diff --git a/metagpt/skills/SummarizeSkill/Topics/config.json b/metagpt/skills/SummarizeSkill/Topics/config.json new file mode 100644 index 000000000..b2cd9985c --- /dev/null +++ b/metagpt/skills/SummarizeSkill/Topics/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Analyze given text or document and extract key topics worth remembering", + "completion": { + "max_tokens": 128, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/SummarizeSkill/Topics/skprompt.txt b/metagpt/skills/SummarizeSkill/Topics/skprompt.txt new file mode 100644 index 000000000..cb7a28c13 --- /dev/null +++ b/metagpt/skills/SummarizeSkill/Topics/skprompt.txt @@ -0,0 +1,28 @@ +Analyze the following extract taken from a document and extract key topics. +- Topics only worth remembering. +- Be brief. Short phrases. +- Can use broken English. +- Conciseness is very important. +- Topics can include names of memories you want to recall. +- NO LONG SENTENCES. SHORT PHRASES. +- Return in JSON +[Input] +My name is Macbeth. I used to be King of Scotland, but I died. My wife's name is Lady Macbeth and we were married for 15 years. We had no children. Our beloved dog Toby McDuff was a famous hunter of rats in the forest. +My tragic story was immortalized by Shakespeare in a play. +[Output] +{ + "topics": [ + "Macbeth", + "King of Scotland", + "Lady Macbeth", + "Dog", + "Toby McDuff", + "Shakespeare", + "Play", + "Tragedy" + ] +} ++++++ +[Input] +{{$input}} +[Output] \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/Acronym/config.json b/metagpt/skills/WriterSkill/Acronym/config.json new file mode 100644 index 000000000..c48414856 --- /dev/null +++ b/metagpt/skills/WriterSkill/Acronym/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Generate an acronym for the given concept or phrase", + "completion": { + "max_tokens": 100, + "temperature": 0.5, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/Acronym/skprompt.txt b/metagpt/skills/WriterSkill/Acronym/skprompt.txt new file mode 100644 index 000000000..1c2e8a6aa --- /dev/null +++ b/metagpt/skills/WriterSkill/Acronym/skprompt.txt @@ -0,0 +1,25 @@ +Generate a suitable acronym pair for the concept. Creativity is encouraged, including obscure references. +The uppercase letters in the acronym expansion must agree with the letters of the acronym + +Q: A technology for detecting moving objects, their distance and velocity using radio waves. +A: R.A.D.A.R: RAdio Detection And Ranging. + +Q: A weapon that uses high voltage electricity to incapacitate the target +A. T.A.S.E.R: Thomas A. Swift’s Electric Rifle + +Q: Equipment that lets a diver breathe underwater +A: S.C.U.B.A: Self Contained Underwater Breathing Apparatus. + +Q: Reminder not to complicated subject matter. +A. K.I.S.S: Keep It Simple Stupid + +Q: A national organization for investment in space travel, rockets, space ships, space exploration +A. N.A.S.A: National Aeronautics Space Administration + +Q: Agreement that governs trade among North American countries. +A: N.A.F.T.A: North American Free Trade Agreement. + +Q: Organization to protect the freedom and security of its member countries in North America and Europe. +A: N.A.T.O: North Atlantic Treaty Organization. + +Q:{{$input}} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/AcronymGenerator/config.json b/metagpt/skills/WriterSkill/AcronymGenerator/config.json new file mode 100644 index 000000000..1dab1fe9f --- /dev/null +++ b/metagpt/skills/WriterSkill/AcronymGenerator/config.json @@ -0,0 +1,15 @@ +{ + "schema": 1, + "type": "completion", + "description": "Given a request to generate an acronym from a string, generate an acronym and provide the acronym explanation.", + "completion": { + "max_tokens": 256, + "temperature": 0.7, + "top_p": 1.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0, + "stop_sequences": [ + "#" + ] + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/AcronymGenerator/skprompt.txt b/metagpt/skills/WriterSkill/AcronymGenerator/skprompt.txt new file mode 100644 index 000000000..5bf0b987d --- /dev/null +++ b/metagpt/skills/WriterSkill/AcronymGenerator/skprompt.txt @@ -0,0 +1,54 @@ +# Name of a super artificial intelligence +J.A.R.V.I.S. = Just A Really Very Intelligent System. +# Name for a new young beautiful assistant +F.R.I.D.A.Y. = Female Replacement Intelligent Digital Assistant Youth. +# Mirror to check what's behind +B.A.R.F. = Binary Augmented Retro-Framing. +# Pair of powerful glasses created by a genius that is now dead +E.D.I.T.H. = Even Dead I’m The Hero. +# A company building and selling computers +I.B.M. = Intelligent Business Machine. +# A super computer that is sentient. +H.A.L = Heuristically programmed ALgorithmic computer. +# an intelligent bot that helps with productivity. +C.O.R.E. = Central Optimization Routines and Efficiency. +# an intelligent bot that helps with productivity. +P.A.L. = Personal Assistant Light. +# an intelligent bot that helps with productivity. +A.I.D.A. = Artificial Intelligence Digital Assistant. +# an intelligent bot that helps with productivity. +H.E.R.A. = Human Emulation and Recognition Algorithm. +# an intelligent bot that helps with productivity. +I.C.A.R.U.S. = Intelligent Control and Automation of Research and Utility Systems. +# an intelligent bot that helps with productivity. +N.E.M.O. = Networked Embedded Multiprocessor Orchestration. +# an intelligent bot that helps with productivity. +E.P.I.C. = Enhanced Productivity and Intelligence through Computing. +# an intelligent bot that helps with productivity. +M.A.I.A. = Multipurpose Artificial Intelligence Assistant. +# an intelligent bot that helps with productivity. +A.R.I.A. = Artificial Reasoning and Intelligent Assistant. +# An incredibly smart entity developed with complex math, that helps me being more productive. +O.M.E.G.A. = Optimized Mathematical Entity for Generalized Artificial intelligence. +# An incredibly smart entity developed with complex math, that helps me being more productive. +P.Y.T.H.O.N. = Precise Yet Thorough Heuristic Optimization Network. +# An incredibly smart entity developed with complex math, that helps me being more productive. +A.P.O.L.L.O. = Adaptive Probabilistic Optimization Learning Library for Online Applications. +# An incredibly smart entity developed with complex math, that helps me being more productive. +S.O.L.I.D. = Self-Organizing Logical Intelligent Data-base. +# An incredibly smart entity developed with complex math, that helps me being more productive. +D.E.E.P. = Dynamic Estimation and Prediction. +# An incredibly smart entity developed with complex math, that helps me being more productive. +B.R.A.I.N. = Biologically Realistic Artificial Intelligence Network. +# An incredibly smart entity developed with complex math, that helps me being more productive. +C.O.G.N.I.T.O. = COmputational and Generalized INtelligence TOolkit. +# An incredibly smart entity developed with complex math, that helps me being more productive. +S.A.G.E. = Symbolic Artificial General Intelligence Engine. +# An incredibly smart entity developed with complex math, that helps me being more productive. +Q.U.A.R.K. = Quantum Universal Algorithmic Reasoning Kernel. +# An incredibly smart entity developed with complex math, that helps me being more productive. +S.O.L.V.E. = Sophisticated Operational Logic and Versatile Expertise. +# An incredibly smart entity developed with complex math, that helps me being more productive. +C.A.L.C.U.L.U.S. = Cognitively Advanced Logic and Computation Unit for Learning and Understanding Systems. + +# {{$INPUT}} diff --git a/metagpt/skills/WriterSkill/AcronymReverse/config.json b/metagpt/skills/WriterSkill/AcronymReverse/config.json new file mode 100644 index 000000000..eed5c5191 --- /dev/null +++ b/metagpt/skills/WriterSkill/AcronymReverse/config.json @@ -0,0 +1,15 @@ +{ + "schema": 1, + "type": "completion", + "description": "Given a single word or acronym, generate the expanded form matching the acronym letters.", + "completion": { + "max_tokens": 256, + "temperature": 0.5, + "top_p": 1.0, + "presence_penalty": 0.8, + "frequency_penalty": 0.0, + "stop_sequences": [ + "#END#" + ] + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/AcronymReverse/skprompt.txt b/metagpt/skills/WriterSkill/AcronymReverse/skprompt.txt new file mode 100644 index 000000000..7c1d649a9 --- /dev/null +++ b/metagpt/skills/WriterSkill/AcronymReverse/skprompt.txt @@ -0,0 +1,24 @@ +# acronym: Devis +Sentences matching the acronym: +1. Dragons Eat Very Interesting Snacks +2. Develop Empathy and Vision to Increase Success +3. Don't Expect Vampires In Supermarkets +#END# + +# acronym: Christmas +Sentences matching the acronym: +1. Celebrating Harmony and Respect in a Season of Togetherness, Merriment, and True joy +2. Children Have Real Interest Since The Mystery And Surprise Thrills +3. Christmas Helps Reduce Inner Stress Through Mistletoe And Sleigh excursions +#END# + +# acronym: noWare +Sentences matching the acronym: +1. No One Wants an App that Randomly Erases everything +2. Nourishing Oatmeal With Almond, Raisin, and Egg toppings +3. Notice Opportunity When Available and React Enthusiastically +#END# + +Reverse the following acronym back to a funny sentence. Provide 3 examples. +# acronym: {{$INPUT}} +Sentences matching the acronym: diff --git a/metagpt/skills/WriterSkill/Brainstorm/config.json b/metagpt/skills/WriterSkill/Brainstorm/config.json new file mode 100644 index 000000000..f50a354e7 --- /dev/null +++ b/metagpt/skills/WriterSkill/Brainstorm/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1, + "type": "completion", + "description": "Given a goal or topic description generate a list of ideas", + "completion": { + "max_tokens": 2000, + "temperature": 0.5, + "top_p": 1.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0, + "stop_sequences": ["##END##"] + }, + "input": { + "parameters": [ + { + "name": "input", + "description": "A topic description or goal.", + "defaultValue": "" + } + ] + } +} diff --git a/metagpt/skills/WriterSkill/Brainstorm/skprompt.txt b/metagpt/skills/WriterSkill/Brainstorm/skprompt.txt new file mode 100644 index 000000000..6a8b92086 --- /dev/null +++ b/metagpt/skills/WriterSkill/Brainstorm/skprompt.txt @@ -0,0 +1,8 @@ +Must: brainstorm ideas and create a list. +Must: use a numbered list. +Must: only one list. +Must: end list with ##END## +Should: no more than 10 items. +Should: at least 3 items. +Topic: {{$INPUT}} +Start. diff --git a/metagpt/skills/WriterSkill/EmailGen/config.json b/metagpt/skills/WriterSkill/EmailGen/config.json new file mode 100644 index 000000000..d43eab348 --- /dev/null +++ b/metagpt/skills/WriterSkill/EmailGen/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Write an email from the given bullet points", + "completion": { + "max_tokens": 256, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/EmailGen/skprompt.txt b/metagpt/skills/WriterSkill/EmailGen/skprompt.txt new file mode 100644 index 000000000..26f4933fb --- /dev/null +++ b/metagpt/skills/WriterSkill/EmailGen/skprompt.txt @@ -0,0 +1,16 @@ +Rewrite my bullet points into complete sentences. Use a polite and inclusive tone. + +[Input] +- Macbeth, King Scotland +- Married, Wife Lady Macbeth, No Kids +- Dog Toby McDuff. Hunter, dead. +- Shakespeare play ++++++ +The story of Macbeth +My name is Macbeth. I used to be King of Scotland, but I died. My wife's name is Lady Macbeth and we were married for 15 years. We had no children. Our beloved dog Toby McDuff was a famous hunter of rats in the forest. +My story was immortalized by Shakespeare in a play. + ++++++ +[Input] +{{$input}} ++++++ diff --git a/metagpt/skills/WriterSkill/EmailTo/config.json b/metagpt/skills/WriterSkill/EmailTo/config.json new file mode 100644 index 000000000..5f0d6ee6e --- /dev/null +++ b/metagpt/skills/WriterSkill/EmailTo/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Turn bullet points into an email to someone, using a polite tone", + "completion": { + "max_tokens": 256, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/EmailTo/skprompt.txt b/metagpt/skills/WriterSkill/EmailTo/skprompt.txt new file mode 100644 index 000000000..cc6b5c962 --- /dev/null +++ b/metagpt/skills/WriterSkill/EmailTo/skprompt.txt @@ -0,0 +1,31 @@ +Rewrite my bullet points into an email featuring complete sentences. Use a polite and inclusive tone. + +[Input] +Toby, + +- Macbeth, King Scotland +- Married, Wife Lady Macbeth, No Kids +- Dog Toby McDuff. Hunter, dead. +- Shakespeare play + +Thanks, +Dexter + ++++++ +Hi Toby, + +The story of Macbeth +My name is Macbeth. I used to be King of Scotland, but I died. My wife's name is Lady Macbeth and we were married for 15 years. We had no children. Our beloved dog Toby McDuff was a famous hunter of rats in the forest. +My story was immortalized by Shakespeare in a play. + +Thanks, +Dexter + ++++++ +[Input] +{{$to}} +{{$input}} + +Thanks, +{{$sender}} ++++++ diff --git a/metagpt/skills/WriterSkill/EnglishImprover/config.json b/metagpt/skills/WriterSkill/EnglishImprover/config.json new file mode 100644 index 000000000..4d10af469 --- /dev/null +++ b/metagpt/skills/WriterSkill/EnglishImprover/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Translate text to English and improve it", + "completion": { + "max_tokens": 3000, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/EnglishImprover/skprompt.txt b/metagpt/skills/WriterSkill/EnglishImprover/skprompt.txt new file mode 100644 index 000000000..09b80036c --- /dev/null +++ b/metagpt/skills/WriterSkill/EnglishImprover/skprompt.txt @@ -0,0 +1,11 @@ +I want you to act as an English translator, spelling corrector and improver. +I will speak to you in any language and you will detect the language, translate it and answer in the corrected and improved version of my text, in English. +I want you to replace my simplified A0-level words and sentences with more beautiful and elegant, upper level English words and sentences. +Keep the meaning same, but make them more literary. +I want you to only reply the correction, the improvements and nothing else, do not write explanations. + +Sentence: """ +{{$INPUT}} +""" + +Translation: diff --git a/metagpt/skills/WriterSkill/NovelChapter/config.json b/metagpt/skills/WriterSkill/NovelChapter/config.json new file mode 100644 index 000000000..3568c6955 --- /dev/null +++ b/metagpt/skills/WriterSkill/NovelChapter/config.json @@ -0,0 +1,36 @@ +{ + "schema": 1, + "type": "completion", + "description": "Write a chapter of a novel.", + "completion": { + "max_tokens": 2048, + "temperature": 0.3, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + }, + "input": { + "parameters": [ + { + "name": "input", + "description": "A synopsis of what the chapter should be about.", + "defaultValue": "" + }, + { + "name": "theme", + "description": "The theme or topic of this novel.", + "defaultValue": "" + }, + { + "name": "previousChapter", + "description": "The synopsis of the previous chapter.", + "defaultValue": "" + }, + { + "name": "chapterIndex", + "description": "The number of the chapter to write.", + "defaultValue": "" + } + ] + } +} diff --git a/metagpt/skills/WriterSkill/NovelChapter/skprompt.txt b/metagpt/skills/WriterSkill/NovelChapter/skprompt.txt new file mode 100644 index 000000000..4fb85a538 --- /dev/null +++ b/metagpt/skills/WriterSkill/NovelChapter/skprompt.txt @@ -0,0 +1,20 @@ +[CONTEXT] + +THEME OF STORY: +{{$theme}} + +PREVIOUS CHAPTER: +{{$previousChapter}} + +[END CONTEXT] + + +WRITE THIS CHAPTER USING [CONTEXT] AND +CHAPTER SYNOPSIS. DO NOT REPEAT SYNOPSIS IN THE OUTPUT + +Chapter Synopsis: +{{$input}} + +Chapter {{$chapterIndex}} + + diff --git a/metagpt/skills/WriterSkill/NovelChapterWithNotes/config.json b/metagpt/skills/WriterSkill/NovelChapterWithNotes/config.json new file mode 100644 index 000000000..02b9e613a --- /dev/null +++ b/metagpt/skills/WriterSkill/NovelChapterWithNotes/config.json @@ -0,0 +1,41 @@ +{ + "schema": 1, + "type": "completion", + "description": "Write a chapter of a novel using notes about the chapter to write.", + "completion": { + "max_tokens": 1024, + "temperature": 0.5, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + }, + "input": { + "parameters": [ + { + "name": "input", + "description": "What the novel should be about.", + "defaultValue": "" + }, + { + "name": "theme", + "description": "The theme of this novel.", + "defaultValue": "" + }, + { + "name": "notes", + "description": "Notes useful to write this chapter.", + "defaultValue": "" + }, + { + "name": "previousChapter", + "description": "The previous chapter synopsis.", + "defaultValue": "" + }, + { + "name": "chapterIndex", + "description": "The number of the chapter to write.", + "defaultValue": "" + } + ] + } +} diff --git a/metagpt/skills/WriterSkill/NovelChapterWithNotes/skprompt.txt b/metagpt/skills/WriterSkill/NovelChapterWithNotes/skprompt.txt new file mode 100644 index 000000000..650bd50d9 --- /dev/null +++ b/metagpt/skills/WriterSkill/NovelChapterWithNotes/skprompt.txt @@ -0,0 +1,19 @@ +[CONTEXT] + +THEME OF STORY: +{{$theme}} + +NOTES OF STORY SO FAR - USE AS REFERENCE +{{$notes}} + +PREVIOUS CHAPTER, USE AS REFERENCE: +{{$previousChapter}} + +[END CONTEXT] + + +WRITE THIS CHAPTER CONTINUING STORY, USING [CONTEXT] AND CHAPTER SYNOPSIS BELOW. DO NOT REPEAT SYNOPSIS IN THE CHAPTER. DON'T REPEAT PREVIOUS CHAPTER. + +{{$input}} + +Chapter {{$chapterIndex}} diff --git a/metagpt/skills/WriterSkill/NovelOutline/config.json b/metagpt/skills/WriterSkill/NovelOutline/config.json new file mode 100644 index 000000000..a34622f7b --- /dev/null +++ b/metagpt/skills/WriterSkill/NovelOutline/config.json @@ -0,0 +1,31 @@ +{ + "schema": 1, + "type": "completion", + "description": "Generate a list of chapter synopsis for a novel or novella", + "completion": { + "max_tokens": 2048, + "temperature": 0.1, + "top_p": 0.5, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + }, + "input": { + "parameters": [ + { + "name": "input", + "description": "What the novel should be about.", + "defaultValue": "" + }, + { + "name": "chapterCount", + "description": "The number of chapters to generate.", + "defaultValue": "" + }, + { + "name": "endMarker", + "description": "The marker to use to end each chapter.", + "defaultValue": "" + } + ] + } +} diff --git a/metagpt/skills/WriterSkill/NovelOutline/skprompt.txt b/metagpt/skills/WriterSkill/NovelOutline/skprompt.txt new file mode 100644 index 000000000..05f725acb --- /dev/null +++ b/metagpt/skills/WriterSkill/NovelOutline/skprompt.txt @@ -0,0 +1,12 @@ +I want to write a {{$chapterCount}} chapter novella about: +{{$input}} + +There MUST BE {{$chapterCount}} CHAPTERS. + +INVENT CHARACTERS AS YOU SEE FIT. BE HIGHLY CREATIVE AND/OR FUNNY. +WRITE SYNOPSIS FOR EACH CHAPTER. INCLUDE INFORMATION ABOUT CHARACTERS ETC. SINCE EACH +CHAPTER WILL BE WRITTEN BY A DIFFERENT WRITER, YOU MUST INCLUDE ALL PERTINENT INFORMATION +IN EACH SYNOPSIS + +YOU MUST END EACH SYNOPSIS WITH {{$endMarker}} + diff --git a/metagpt/skills/WriterSkill/Rewrite/config.json b/metagpt/skills/WriterSkill/Rewrite/config.json new file mode 100644 index 000000000..175ade9d9 --- /dev/null +++ b/metagpt/skills/WriterSkill/Rewrite/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Automatically generate compact notes for any text or text document", + "completion": { + "max_tokens": 256, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/Rewrite/skprompt.txt b/metagpt/skills/WriterSkill/Rewrite/skprompt.txt new file mode 100644 index 000000000..37f8d03fc --- /dev/null +++ b/metagpt/skills/WriterSkill/Rewrite/skprompt.txt @@ -0,0 +1,6 @@ +Rewrite the given text like it was written in this style or by: {{$style}}. +MUST RETAIN THE MEANING AND FACTUAL CONTENT AS THE ORIGINAL. + + +{{$input}} + diff --git a/metagpt/skills/WriterSkill/ShortPoem/config.json b/metagpt/skills/WriterSkill/ShortPoem/config.json new file mode 100644 index 000000000..0cc3da6c8 --- /dev/null +++ b/metagpt/skills/WriterSkill/ShortPoem/config.json @@ -0,0 +1,21 @@ +{ + "schema": 1, + "type": "completion", + "description": "Turn a scenario into a short and entertaining poem.", + "completion": { + "max_tokens": 60, + "temperature": 0.5, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + }, + "input": { + "parameters": [ + { + "name": "input", + "description": "The scenario to turn into a poem.", + "defaultValue": "" + } + ] + } +} diff --git a/metagpt/skills/WriterSkill/ShortPoem/skprompt.txt b/metagpt/skills/WriterSkill/ShortPoem/skprompt.txt new file mode 100644 index 000000000..bc42fcba6 --- /dev/null +++ b/metagpt/skills/WriterSkill/ShortPoem/skprompt.txt @@ -0,0 +1,2 @@ +Generate a short funny poem or limerick to explain the given event. Be creative and be funny. Let your imagination run wild. +Event:{{$input}} diff --git a/metagpt/skills/WriterSkill/StoryGen/config.json b/metagpt/skills/WriterSkill/StoryGen/config.json new file mode 100644 index 000000000..212831341 --- /dev/null +++ b/metagpt/skills/WriterSkill/StoryGen/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Generate a list of synopsis for a novel or novella with sub-chapters", + "completion": { + "max_tokens": 250, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} diff --git a/metagpt/skills/WriterSkill/StoryGen/skprompt.txt b/metagpt/skills/WriterSkill/StoryGen/skprompt.txt new file mode 100644 index 000000000..661df013c --- /dev/null +++ b/metagpt/skills/WriterSkill/StoryGen/skprompt.txt @@ -0,0 +1,10 @@ +ONLY USE XML TAGS IN THIS LIST: +[XML TAG LIST] +list: Surround any lists with this tag +synopsis: An outline of the chapter to write +[END LIST] + +EMIT WELL FORMED XML ALWAYS. Code should be CDATA. + + +{{$input}} diff --git a/metagpt/skills/WriterSkill/TellMeMore/config.json b/metagpt/skills/WriterSkill/TellMeMore/config.json new file mode 100644 index 000000000..28b6b4e5c --- /dev/null +++ b/metagpt/skills/WriterSkill/TellMeMore/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Summarize given text or any text document", + "completion": { + "max_tokens": 500, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/TellMeMore/skprompt.txt b/metagpt/skills/WriterSkill/TellMeMore/skprompt.txt new file mode 100644 index 000000000..143ce3a65 --- /dev/null +++ b/metagpt/skills/WriterSkill/TellMeMore/skprompt.txt @@ -0,0 +1,7 @@ +>>>>>The following is part of a {{$conversationtype}}. +{{$input}} + +>>>>>The following is an overview of a previous part of the {{$conversationtype}}, focusing on "{{$focusarea}}". +{{$previousresults}} + +>>>>>In 250 words or less, write a verbose and detailed overview of the {{$conversationtype}} focusing solely on "{{$focusarea}}". \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/Translate/config.json b/metagpt/skills/WriterSkill/Translate/config.json new file mode 100644 index 000000000..8134ce8dd --- /dev/null +++ b/metagpt/skills/WriterSkill/Translate/config.json @@ -0,0 +1,15 @@ +{ + "schema": 1, + "type": "completion", + "description": "Translate the input into a language of your choice", + "completion": { + "max_tokens": 2000, + "temperature": 0.7, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0, + "stop_sequences": [ + "[done]" + ] + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/Translate/skprompt.txt b/metagpt/skills/WriterSkill/Translate/skprompt.txt new file mode 100644 index 000000000..d5f2fa8c1 --- /dev/null +++ b/metagpt/skills/WriterSkill/Translate/skprompt.txt @@ -0,0 +1,7 @@ +Translate the input below into {{$language}} + +MAKE SURE YOU ONLY USE {{$language}}. + +{{$input}} + +Translation: diff --git a/metagpt/skills/WriterSkill/TwoSentenceSummary/config.json b/metagpt/skills/WriterSkill/TwoSentenceSummary/config.json new file mode 100644 index 000000000..833bd5950 --- /dev/null +++ b/metagpt/skills/WriterSkill/TwoSentenceSummary/config.json @@ -0,0 +1,12 @@ +{ + "schema": 1, + "type": "completion", + "description": "Summarize given text in two sentences or less", + "completion": { + "max_tokens": 100, + "temperature": 0.0, + "top_p": 0.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0 + } +} \ No newline at end of file diff --git a/metagpt/skills/WriterSkill/TwoSentenceSummary/skprompt.txt b/metagpt/skills/WriterSkill/TwoSentenceSummary/skprompt.txt new file mode 100644 index 000000000..b8f657a93 --- /dev/null +++ b/metagpt/skills/WriterSkill/TwoSentenceSummary/skprompt.txt @@ -0,0 +1,4 @@ +Summarize the following text in two sentences or less. +[BEGIN TEXT] +{{$input}} +[END TEXT] diff --git a/metagpt/tools/search_engine.py b/metagpt/tools/search_engine.py index d28700054..4ac078714 100644 --- a/metagpt/tools/search_engine.py +++ b/metagpt/tools/search_engine.py @@ -5,15 +5,32 @@ @Author : alexanderwu @File : search_engine.py """ -from __future__ import annotations +# from __future__ import annotations import importlib from typing import Callable, Coroutine, Literal, overload +from semantic_kernel.skill_definition import sk_function + from metagpt.config import CONFIG from metagpt.tools import SearchEngineType +class SkSearchEngine: + def __init__(self): + self.search_engine = SearchEngine() + + @sk_function( + description="searches results from Google. Useful when you need to find short " + "and succinct answers about a specific topic. Input should be a search query.", + name="searchAsync", + input_description="search", + ) + async def run(self, query: str) -> str: + result = await self.search_engine.run(query) + return result + + class SearchEngine: """Class representing a search engine. @@ -25,6 +42,7 @@ class SearchEngine: run_func: The function to run the search. engine: The search engine type. """ + def __init__( self, engine: SearchEngineType | None = None, @@ -33,7 +51,7 @@ class SearchEngine: engine = engine or CONFIG.search_engine if engine == SearchEngineType.SERPAPI_GOOGLE: module = "metagpt.tools.search_engine_serpapi" - run_func = importlib.import_module(module).SerpAPIWrapper().run + run_func = importlib.import_module(module).SerpAPIWrapper().run elif engine == SearchEngineType.SERPER_GOOGLE: module = "metagpt.tools.search_engine_serper" run_func = importlib.import_module(module).SerperWrapper().run diff --git a/metagpt/tools/ut_writer.py b/metagpt/tools/ut_writer.py index 263a0269e..43ca72150 100644 --- a/metagpt/tools/ut_writer.py +++ b/metagpt/tools/ut_writer.py @@ -60,6 +60,7 @@ def test_node_tags(project_key, nodes, operations, expected_msg): # 3. If comments are needed, use Chinese. # If you understand, please wait for me to give the interface definition and just answer "Understood" to save tokens. +''' ACT_PROMPT_PREFIX = '''Refer to the test types: such as missing request parameters, field boundary verification, incorrect field type. Please output 10 test cases within one `@pytest.mark.parametrize` scope. @@ -94,7 +95,8 @@ Name Type Required Default Value Remarks code integer Yes message string Yes data object Yes - +``` +''' class UTGenerator: diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index a0dabd77e..5f94de066 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -191,7 +191,8 @@ class CodeParser: else: logger.error(f"{pattern} not match following text:") logger.error(text) - raise Exception + # raise Exception + return "" return code @classmethod diff --git a/metagpt/utils/file.py b/metagpt/utils/file.py new file mode 100644 index 000000000..5aca2a0e5 --- /dev/null +++ b/metagpt/utils/file.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/4 15:40:40 +@Author : Stitch-z +@File : file.py +@Describe : General file operations. +""" +import aiofiles +from pathlib import Path + +from metagpt.logs import logger + + +class File: + """A general util for file operations.""" + + @classmethod + async def write(cls, root_path: Path, filename: str, content: bytes) -> Path: + """Write the file content to the local specified path. + + Args: + root_path: The root path of file, such as "/data". + filename: The name of file, such as "test.txt". + content: The binary content of file. + + Returns: + The full filename of file, such as "/data/test.txt". + + Raises: + Exception: If an unexpected error occurs during the file writing process. + """ + try: + root_path.mkdir(parents=True, exist_ok=True) + full_path = root_path / filename + async with aiofiles.open(full_path, mode="wb") as writer: + await writer.write(content) + logger.info(f"Successfully write file: {full_path}") + return full_path + except Exception as e: + logger.error(f"Error writing file: {e}") + raise e \ No newline at end of file diff --git a/metagpt/utils/index.html b/metagpt/utils/index.html new file mode 100644 index 000000000..d750a1b6a --- /dev/null +++ b/metagpt/utils/index.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/metagpt/utils/make_sk_kernel.py b/metagpt/utils/make_sk_kernel.py new file mode 100644 index 000000000..5e919abeb --- /dev/null +++ b/metagpt/utils/make_sk_kernel.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/13 12:29 +@Author : femto Zheng +@File : make_sk_kernel.py +""" +import semantic_kernel as sk +from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import ( + AzureChatCompletion, +) +from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import ( + OpenAIChatCompletion, +) + +from metagpt.config import CONFIG + + +def make_sk_kernel(): + kernel = sk.Kernel() + if CONFIG.openai_api_type == "azure": + kernel.add_chat_service( + "chat_completion", + AzureChatCompletion(CONFIG.deployment_name, CONFIG.openai_api_base, CONFIG.openai_api_key), + ) + else: + kernel.add_chat_service( + "chat_completion", + OpenAIChatCompletion( + CONFIG.openai_api_model, CONFIG.openai_api_key, org_id=None, endpoint=CONFIG.openai_api_base + ), + ) + + return kernel diff --git a/metagpt/utils/mermaid.py b/metagpt/utils/mermaid.py index 24aabe8ae..d2cce3965 100644 --- a/metagpt/utils/mermaid.py +++ b/metagpt/utils/mermaid.py @@ -2,9 +2,10 @@ # -*- coding: utf-8 -*- """ @Time : 2023/7/4 10:53 -@Author : alexanderwu +@Author : alexanderwu alitrack @File : mermaid.py """ +import asyncio import subprocess from pathlib import Path @@ -12,48 +13,76 @@ from metagpt.config import CONFIG from metagpt.const import PROJECT_ROOT from metagpt.logs import logger from metagpt.utils.common import check_cmd_exists +import os +import sys - -def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height=2048) -> int: +async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height=2048) -> int: """suffix: png/svg/pdf :param mermaid_code: mermaid code :param output_file_without_suffix: output filename :param width: :param height: - :return: 0 if succed, -1 if failed + :return: 0 if succeed, -1 if failed """ # Write the Mermaid code to a temporary file + dir_name = os.path.dirname(output_file_without_suffix) + if dir_name and not os.path.exists(dir_name): + os.makedirs(dir_name) tmp = Path(f"{output_file_without_suffix}.mmd") tmp.write_text(mermaid_code, encoding="utf-8") + + engine = CONFIG.mermaid_engine.lower() + if engine == "nodejs": + if check_cmd_exists("mmdc") != 0: + logger.warning("RUN `npm install -g @mermaid-js/mermaid-cli` to install mmdc") + return -1 + + for suffix in ["pdf", "svg", "png"]: + output_file = f"{output_file_without_suffix}.{suffix}" + # Call the `mmdc` command to convert the Mermaid code to a PNG + logger.info(f"Generating {output_file}..") - if check_cmd_exists("mmdc") != 0: - logger.warning("RUN `npm install -g @mermaid-js/mermaid-cli` to install mmdc") - return -1 - - for suffix in ["pdf", "svg", "png"]: - output_file = f"{output_file_without_suffix}.{suffix}" - # Call the `mmdc` command to convert the Mermaid code to a PNG - logger.info(f"Generating {output_file}..") - - if CONFIG.puppeteer_config: - subprocess.run( - [ - CONFIG.mmdc, - "-p", - CONFIG.puppeteer_config, - "-i", - str(tmp), - "-o", - output_file, - "-w", - str(width), - "-H", - str(height), - ] + if CONFIG.puppeteer_config: + commands =[ + CONFIG.mmdc, + "-p", + CONFIG.puppeteer_config, + "-i", + str(tmp), + "-o", + output_file, + "-w", + str(width), + "-H", + str(height), + ] + else: + commands =[CONFIG.mmdc, "-i", str(tmp), "-o", output_file, "-w", str(width), "-H", str(height)] + process = await asyncio.create_subprocess_exec( + *commands, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE ) + + stdout, stderr = await process.communicate() + if stdout: + logger.info(stdout.decode()) + if stderr: + logger.error(stderr.decode()) + else: + + if engine =='playwright': + from metagpt.utils.mmdc_playwright import mermaid_to_file + return await mermaid_to_file(mermaid_code, output_file_without_suffix, width, height) + elif engine =='pyppeteer': + from metagpt.utils.mmdc_pyppeteer import mermaid_to_file + return await mermaid_to_file(mermaid_code, output_file_without_suffix, width, height) + elif engine =='ink': + from metagpt.utils.mmdc_ink import mermaid_to_file + return await mermaid_to_file(mermaid_code, output_file_without_suffix) else: - subprocess.run([CONFIG.mmdc, "-i", str(tmp), "-o", output_file, "-w", str(width), "-H", str(height)]) + logger.warning(f"Unsupported mermaid engine: {engine}") return 0 @@ -108,7 +137,9 @@ MMC2 = """sequenceDiagram SE-->>M: return summary""" + if __name__ == "__main__": - # logger.info(print_members(print_members)) - mermaid_to_file(MMC1, PROJECT_ROOT / "tmp/1.png") - mermaid_to_file(MMC2, PROJECT_ROOT / "tmp/2.png") + loop = asyncio.new_event_loop() + result = loop.run_until_complete(mermaid_to_file(MMC1, PROJECT_ROOT / f"{CONFIG.mermaid_engine}/1")) + result = loop.run_until_complete(mermaid_to_file(MMC2, PROJECT_ROOT / f"{CONFIG.mermaid_engine}/1")) + loop.close() diff --git a/metagpt/utils/mmdc_ink.py b/metagpt/utils/mmdc_ink.py new file mode 100644 index 000000000..3d91cde9d --- /dev/null +++ b/metagpt/utils/mmdc_ink.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/4 16:12 +@Author : alitrack +@File : mermaid.py +""" +import base64 +import os + +from aiohttp import ClientSession,ClientError +from metagpt.logs import logger + + +async def mermaid_to_file(mermaid_code, output_file_without_suffix): + """suffix: png/svg + :param mermaid_code: mermaid code + :param output_file_without_suffix: output filename without suffix + :return: 0 if succeed, -1 if failed + """ + encoded_string = base64.b64encode(mermaid_code.encode()).decode() + + for suffix in ["svg", "png"]: + output_file = f"{output_file_without_suffix}.{suffix}" + path_type = "svg" if suffix == "svg" else "img" + url = f"https://mermaid.ink/{path_type}/{encoded_string}" + async with ClientSession() as session: + try: + async with session.get(url) as response: + if response.status == 200: + text = await response.content.read() + with open(output_file, 'wb') as f: + f.write(text) + logger.info(f"Generating {output_file}..") + else: + logger.error(f"Failed to generate {output_file}") + return -1 + except ClientError as e: + logger.error(f"network error: {e}") + return -1 + return 0 diff --git a/metagpt/utils/mmdc_playwright.py b/metagpt/utils/mmdc_playwright.py new file mode 100644 index 000000000..bdbfd82ff --- /dev/null +++ b/metagpt/utils/mmdc_playwright.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/4 16:12 +@Author : Steven Lee +@File : mmdc_playwright.py +""" + +import os +from urllib.parse import urljoin +from playwright.async_api import async_playwright +from metagpt.logs import logger + +async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height=2048)-> int: + """ + Converts the given Mermaid code to various output formats and saves them to files. + + Args: + mermaid_code (str): The Mermaid code to convert. + output_file_without_suffix (str): The output file name without the file extension. + width (int, optional): The width of the output image in pixels. Defaults to 2048. + height (int, optional): The height of the output image in pixels. Defaults to 2048. + + Returns: + int: Returns 1 if the conversion and saving were successful, -1 otherwise. + """ + suffixes=['png', 'svg', 'pdf'] + __dirname = os.path.dirname(os.path.abspath(__file__)) + + async with async_playwright() as p: + browser = await p.chromium.launch() + device_scale_factor = 1.0 + context = await browser.new_context( + viewport={'width': width, 'height': height}, + device_scale_factor=device_scale_factor, + ) + page = await context.new_page() + + async def console_message(msg): + logger.info(msg.text) + page.on('console', console_message) + + try: + await page.set_viewport_size({'width': width, 'height': height}) + + mermaid_html_path = os.path.abspath( + os.path.join(__dirname, 'index.html')) + mermaid_html_url = urljoin('file:', mermaid_html_path) + await page.goto(mermaid_html_url) + await page.wait_for_load_state("networkidle") + + await page.wait_for_selector("div#container", state="attached") + mermaid_config = {} + background_color = "#ffffff" + my_css = "" + await page.evaluate(f'document.body.style.background = "{background_color}";') + + metadata = await page.evaluate('''async ([definition, mermaidConfig, myCSS, backgroundColor]) => { + const { mermaid, zenuml } = globalThis; + await mermaid.registerExternalDiagrams([zenuml]); + mermaid.initialize({ startOnLoad: false, ...mermaidConfig }); + const { svg } = await mermaid.render('my-svg', definition, document.getElementById('container')); + document.getElementById('container').innerHTML = svg; + const svgElement = document.querySelector('svg'); + svgElement.style.backgroundColor = backgroundColor; + + if (myCSS) { + const style = document.createElementNS('http://www.w3.org/2000/svg', 'style'); + style.appendChild(document.createTextNode(myCSS)); + svgElement.appendChild(style); + } + + }''', [mermaid_code, mermaid_config, my_css, background_color]) + + if 'svg' in suffixes : + svg_xml = await page.evaluate('''() => { + const svg = document.querySelector('svg'); + const xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(svg); + }''') + logger.info(f"Generating {output_file_without_suffix}.svg..") + with open(f'{output_file_without_suffix}.svg', 'wb') as f: + f.write(svg_xml.encode('utf-8')) + + if 'png' in suffixes: + clip = await page.evaluate('''() => { + const svg = document.querySelector('svg'); + const rect = svg.getBoundingClientRect(); + return { + x: Math.floor(rect.left), + y: Math.floor(rect.top), + width: Math.ceil(rect.width), + height: Math.ceil(rect.height) + }; + }''') + await page.set_viewport_size({'width': clip['x'] + clip['width'], 'height': clip['y'] + clip['height']}) + screenshot = await page.screenshot(clip=clip, omit_background=True, scale='device') + logger.info(f"Generating {output_file_without_suffix}.png..") + with open(f'{output_file_without_suffix}.png', 'wb') as f: + f.write(screenshot) + if 'pdf' in suffixes: + pdf_data = await page.pdf(scale=device_scale_factor) + logger.info(f"Generating {output_file_without_suffix}.pdf..") + with open(f'{output_file_without_suffix}.pdf', 'wb') as f: + f.write(pdf_data) + return 0 + except Exception as e: + logger.error(e) + return -1 + finally: + await browser.close() diff --git a/metagpt/utils/mmdc_pyppeteer.py b/metagpt/utils/mmdc_pyppeteer.py new file mode 100644 index 000000000..7ec30fd12 --- /dev/null +++ b/metagpt/utils/mmdc_pyppeteer.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/4 16:12 +@Author : alitrack +@File : mmdc_pyppeteer.py +""" +import os +from urllib.parse import urljoin +from pyppeteer import launch +from metagpt.logs import logger +from metagpt.config import CONFIG + +async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height=2048)-> int: + """ + Converts the given Mermaid code to various output formats and saves them to files. + + Args: + mermaid_code (str): The Mermaid code to convert. + output_file_without_suffix (str): The output file name without the file extension. + width (int, optional): The width of the output image in pixels. Defaults to 2048. + height (int, optional): The height of the output image in pixels. Defaults to 2048. + + Returns: + int: Returns 1 if the conversion and saving were successful, -1 otherwise. + """ + suffixes = ['png', 'svg', 'pdf'] + __dirname = os.path.dirname(os.path.abspath(__file__)) + + + if CONFIG.pyppeteer_executable_path: + browser = await launch(headless=True, + executablePath=CONFIG.pyppeteer_executable_path, + args=['--disable-extensions',"--no-sandbox"] + ) + else: + logger.error("Please set the environment variable:PYPPETEER_EXECUTABLE_PATH.") + return -1 + page = await browser.newPage() + device_scale_factor = 1.0 + + async def console_message(msg): + logger.info(msg.text) + page.on('console', console_message) + + try: + await page.setViewport(viewport={'width': width, 'height': height, 'deviceScaleFactor': device_scale_factor}) + + mermaid_html_path = os.path.abspath( + os.path.join(__dirname, 'index.html')) + mermaid_html_url = urljoin('file:', mermaid_html_path) + await page.goto(mermaid_html_url) + + await page.querySelector("div#container") + mermaid_config = {} + background_color = "#ffffff" + my_css = "" + await page.evaluate(f'document.body.style.background = "{background_color}";') + + metadata = await page.evaluate('''async ([definition, mermaidConfig, myCSS, backgroundColor]) => { + const { mermaid, zenuml } = globalThis; + await mermaid.registerExternalDiagrams([zenuml]); + mermaid.initialize({ startOnLoad: false, ...mermaidConfig }); + const { svg } = await mermaid.render('my-svg', definition, document.getElementById('container')); + document.getElementById('container').innerHTML = svg; + const svgElement = document.querySelector('svg'); + svgElement.style.backgroundColor = backgroundColor; + + if (myCSS) { + const style = document.createElementNS('http://www.w3.org/2000/svg', 'style'); + style.appendChild(document.createTextNode(myCSS)); + svgElement.appendChild(style); + } + }''', [mermaid_code, mermaid_config, my_css, background_color]) + + if 'svg' in suffixes : + svg_xml = await page.evaluate('''() => { + const svg = document.querySelector('svg'); + const xmlSerializer = new XMLSerializer(); + return xmlSerializer.serializeToString(svg); + }''') + logger.info(f"Generating {output_file_without_suffix}.svg..") + with open(f'{output_file_without_suffix}.svg', 'wb') as f: + f.write(svg_xml.encode('utf-8')) + + if 'png' in suffixes: + clip = await page.evaluate('''() => { + const svg = document.querySelector('svg'); + const rect = svg.getBoundingClientRect(); + return { + x: Math.floor(rect.left), + y: Math.floor(rect.top), + width: Math.ceil(rect.width), + height: Math.ceil(rect.height) + }; + }''') + await page.setViewport({'width': clip['x'] + clip['width'], 'height': clip['y'] + clip['height'], 'deviceScaleFactor': device_scale_factor}) + screenshot = await page.screenshot(clip=clip, omit_background=True, scale='device') + logger.info(f"Generating {output_file_without_suffix}.png..") + with open(f'{output_file_without_suffix}.png', 'wb') as f: + f.write(screenshot) + if 'pdf' in suffixes: + pdf_data = await page.pdf(scale=device_scale_factor) + logger.info(f"Generating {output_file_without_suffix}.pdf..") + with open(f'{output_file_without_suffix}.pdf', 'wb') as f: + f.write(pdf_data) + return 0 + except Exception as e: + logger.error(e) + return -1 + finally: + await browser.close() + diff --git a/setup.py b/setup.py index a88f9de92..f9ae768e6 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ setup( "selenium": ["selenium>4", "webdriver_manager", "beautifulsoup4"], "search-google": ["google-api-python-client==2.94.0"], "search-ddg": ["duckduckgo-search==3.8.5"], + "pyppeteer": ["pyppeteer>=1.0.2"], }, cmdclass={ "install_mermaid": InstallMermaidCLI, diff --git a/startup.py b/startup.py index e6d5fc4e9..e2a903c9b 100644 --- a/startup.py +++ b/startup.py @@ -1,11 +1,16 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import asyncio -import platform + import fire -from metagpt.roles import Architect, Engineer, ProductManager -from metagpt.roles import ProjectManager, QaEngineer +from metagpt.roles import ( + Architect, + Engineer, + ProductManager, + ProjectManager, + QaEngineer, +) from metagpt.software_company import SoftwareCompany @@ -15,15 +20,17 @@ async def startup( n_round: int = 5, code_review: bool = False, run_tests: bool = False, - implement: bool = True + implement: bool = True, ): """Run a startup. Be a boss.""" company = SoftwareCompany() - company.hire([ - ProductManager(), - Architect(), - ProjectManager(), - ]) + company.hire( + [ + ProductManager(), + Architect(), + ProjectManager(), + ] + ) # if implement or code_review if implement or code_review: @@ -46,7 +53,7 @@ def main( n_round: int = 5, code_review: bool = True, run_tests: bool = False, - implement: bool = True + implement: bool = True, ): """ We are a software startup comprised of AI. By investing in us, @@ -58,12 +65,8 @@ def main( :param code_review: Whether to use code review. :return: """ - if platform.system() == "Windows": - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - asyncio.run(startup(idea, investment, n_round, - code_review, run_tests, implement)) + asyncio.run(startup(idea, investment, n_round, code_review, run_tests, implement)) -if __name__ == '__main__': +if __name__ == "__main__": fire.Fire(main) - diff --git a/tests/metagpt/actions/test_write_tutorial.py b/tests/metagpt/actions/test_write_tutorial.py new file mode 100644 index 000000000..683fee082 --- /dev/null +++ b/tests/metagpt/actions/test_write_tutorial.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/6 21:41:34 +@Author : Stitch-z +@File : test_write_tutorial.py +""" +from typing import Dict + +import pytest + +from metagpt.actions.write_tutorial import WriteDirectory, WriteContent + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("language", "topic"), + [("English", "Write a tutorial about Python")] +) +async def test_write_directory(language: str, topic: str): + ret = await WriteDirectory(language=language).run(topic=topic) + assert isinstance(ret, dict) + assert "title" in ret + assert "directory" in ret + assert isinstance(ret["directory"], list) + assert len(ret["directory"]) + assert isinstance(ret["directory"][0], dict) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("language", "topic", "directory"), + [("English", "Write a tutorial about Python", {"Introduction": ["What is Python?", "Why learn Python?"]})] +) +async def test_write_content(language: str, topic: str, directory: Dict): + ret = await WriteContent(language=language, directory=directory).run(topic=topic) + assert isinstance(ret, str) + assert list(directory.keys())[0] in ret + for value in list(directory.values())[0]: + assert value in ret diff --git a/tests/metagpt/planner/__init__.py b/tests/metagpt/planner/__init__.py new file mode 100644 index 000000000..85e01b36b --- /dev/null +++ b/tests/metagpt/planner/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/16 20:03 +@Author : femto Zheng +@File : __init__.py +""" diff --git a/tests/metagpt/planner/test_action_planner.py b/tests/metagpt/planner/test_action_planner.py new file mode 100644 index 000000000..5ab9a493f --- /dev/null +++ b/tests/metagpt/planner/test_action_planner.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/16 20:03 +@Author : femto Zheng +@File : test_basic_planner.py +""" +import pytest +from semantic_kernel.core_skills import FileIOSkill, MathSkill, TextSkill, TimeSkill +from semantic_kernel.planning.action_planner.action_planner import ActionPlanner + +from metagpt.actions import BossRequirement +from metagpt.roles.sk_agent import SkAgent +from metagpt.schema import Message + + +@pytest.mark.asyncio +async def test_action_planner(): + role = SkAgent(planner_cls=ActionPlanner) + # let's give the agent 4 skills + role.import_skill(MathSkill(), "math") + role.import_skill(FileIOSkill(), "fileIO") + role.import_skill(TimeSkill(), "time") + role.import_skill(TextSkill(), "text") + task = "What is the sum of 110 and 990?" + role.recv(Message(content=task, cause_by=BossRequirement)) + + await role._think() # it will choose mathskill.Add + assert "1100" == (await role._act()).content diff --git a/tests/metagpt/planner/test_basic_planner.py b/tests/metagpt/planner/test_basic_planner.py new file mode 100644 index 000000000..03a82ec5e --- /dev/null +++ b/tests/metagpt/planner/test_basic_planner.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/9/16 20:03 +@Author : femto Zheng +@File : test_basic_planner.py +""" +import pytest +from semantic_kernel.core_skills import TextSkill + +from metagpt.actions import BossRequirement +from metagpt.const import SKILL_DIRECTORY +from metagpt.roles.sk_agent import SkAgent +from metagpt.schema import Message + + +@pytest.mark.asyncio +async def test_basic_planner(): + task = """ + Tomorrow is Valentine's day. I need to come up with a few date ideas. She speaks French so write it in French. + Convert the text to uppercase""" + role = SkAgent() + + # let's give the agent some skills + role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "SummarizeSkill") + role.import_semantic_skill_from_directory(SKILL_DIRECTORY, "WriterSkill") + role.import_skill(TextSkill(), "TextSkill") + # using BasicPlanner + role.recv(Message(content=task, cause_by=BossRequirement)) + await role._think() + # assuming sk_agent will think he needs WriterSkill.Brainstorm and WriterSkill.Translate + assert "WriterSkill.Brainstorm" in role.plan.generated_plan.result + assert "WriterSkill.Translate" in role.plan.generated_plan.result + # assert "SALUT" in (await role._act()).content #content will be some French diff --git a/tests/metagpt/roles/test_tutorial_assistant.py b/tests/metagpt/roles/test_tutorial_assistant.py new file mode 100644 index 000000000..945620cfc --- /dev/null +++ b/tests/metagpt/roles/test_tutorial_assistant.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/6 23:11:27 +@Author : Stitch-z +@File : test_tutorial_assistant.py +""" +import aiofiles +import pytest + +from metagpt.roles.tutorial_assistant import TutorialAssistant + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("language", "topic"), + [("Chinese", "Write a tutorial about Python")] +) +async def test_tutorial_assistant(language: str, topic: str): + topic = "Write a tutorial about MySQL" + role = TutorialAssistant(language=language) + msg = await role.run(topic) + filename = msg.content + title = filename.split("/")[-1].split(".")[0] + async with aiofiles.open(filename, mode="r") as reader: + content = await reader.read() + assert content.startswith(f"# {title}") \ No newline at end of file diff --git a/tests/metagpt/utils/test_file.py b/tests/metagpt/utils/test_file.py new file mode 100644 index 000000000..a9f1a353d --- /dev/null +++ b/tests/metagpt/utils/test_file.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# _*_ coding: utf-8 _*_ +""" +@Time : 2023/9/4 15:40:40 +@Author : Stitch-z +@File : test_file.py +""" +from pathlib import Path + +import aiofiles +import pytest + +from metagpt.utils.file import File + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("root_path", "filename", "content"), + [(Path("/code/MetaGPT/data/tutorial_docx/2023-09-07_17-05-20"), "test.md", "Hello World!")] +) +async def test_write_file(root_path: Path, filename: str, content: bytes): + full_file_name = await File.write(root_path=root_path, filename=filename, content=content.encode('utf-8')) + assert isinstance(full_file_name, Path) + assert root_path / filename == full_file_name + async with aiofiles.open(full_file_name, mode="r") as reader: + body = await reader.read() + assert body == content \ No newline at end of file