From 9d1a261bf626502a0ac3ee2406a0e7d688c41070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 11:42:13 +0800 Subject: [PATCH 01/41] feat: + education industry --- .gitignore | 3 + metagpt/actions/write_teaching_plan.py | 134 +++++++++++++++++++++++++ metagpt/provider/openai_api.py | 20 ++-- metagpt/roles/role.py | 66 +++++++++++- metagpt/roles/teacher.py | 96 ++++++++++++++++++ metagpt/software_company.py | 5 +- requirements.txt | 1 + startup.py | 96 ++++++++++++++++-- tests/metagpt/roles/test_teacher.py | 94 +++++++++++++++++ 9 files changed, 498 insertions(+), 17 deletions(-) create mode 100644 metagpt/actions/write_teaching_plan.py create mode 100644 metagpt/roles/teacher.py create mode 100644 tests/metagpt/roles/test_teacher.py diff --git a/.gitignore b/.gitignore index c4c79c733..3ec71f8b6 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,6 @@ workspace/* *.mmd tmp output.wav + +# output folder +output diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py new file mode 100644 index 000000000..1f0167df3 --- /dev/null +++ b/metagpt/actions/write_teaching_plan.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/11 14:43 +@Author : mashenquan +@File : write_teaching_plan.py +""" +from langchain.llms.base import LLM +from metagpt.logs import logger +from metagpt.actions import Action +from metagpt.schema import Message + + +class TeachingPlanRequirement(Action): + """Teaching Plan Requirement without any implementation details""" + + async def run(self, *args, **kwargs): + raise NotImplementedError + + +class WriteTeachingPlanPart(Action): + """Write Teaching Plan Part""" + + def __init__(self, name: str = '', context=None, llm: LLM = None, topic="", language="Chinese"): + """ + + Args: + name: action name + context: context + llm: object of :class:`LLM` + topic: topic part of teaching plan + """ + super().__init__(name, context, llm) + self.topic = topic + self.language = language + self.rsp = None + + async def run(self, *args, **kwargs): + if len(args) < 1 or len(args[0]) < 1 or not isinstance(args[0][0], Message): + raise ValueError("Invalid args, a tuple of List[Message] is expected") + + statements = self.TOPIC_STATEMENTS.get(self.topic, []) + formatter = self.PROMPT_TITLE_TEMPLATE if self.topic == self.COURSE_TITLE else self.PROMPT_TEMPLATE + prompt = formatter.format(formation=self.FORMATION, + role=self.prefix, + statements="\n".join(statements), + lesson=args[0][0].content, + topic=self.topic, + language=self.language) + + logger.debug(prompt) + rsp = await self._aask(prompt=prompt) + logger.debug(rsp) + self._set_result(rsp) + return self.rsp + + def _set_result(self, rsp): + if self.DATA_BEGIN_TAG in rsp: + ix = rsp.index(self.DATA_BEGIN_TAG) + rsp = rsp[ix + len(self.DATA_BEGIN_TAG):] + if self.DATA_END_TAG in rsp: + ix = rsp.index(self.DATA_END_TAG) + rsp = rsp[0:ix] + self.rsp = rsp.strip() + if self.topic != self.COURSE_TITLE: + return + if '#' not in self.rsp or self.rsp.index('#') != 0: + self.rsp = "# " + self.rsp + + def __str__(self): + """str()时返回`topic`""" + return self.topic + + def __repr__(self): + """调试时返回`topic`""" + return self.topic + + FORMATION = """ + "\tCapacity and role" defines the role you are currently playing; + "\t[LESSON_BEGIN]" and "[LESSON_END]" tags enclose the content of textbook; + "\tStatement" defines the work detail you need to complete at this stage; + "\tAnswer options" defines the format requirements for your responses; + "\tConstraint" defines the conditions that your responses must comply with. + """ + COURSE_TITLE = "Title" + TOPICS = [COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", + "Teaching Methods and Strategies", "Learning Activities", + "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement"] + + TOPIC_STATEMENTS = { + COURSE_TITLE: ["Statement: Find and return the title of the lesson only in markdown first-level header format, " + "without anything else."], + "Teaching Content": [ + "Statement: \"Teaching Content\" must include vocabulary, analysis, and examples of various grammar " + "structures that appear in the textbook, as well as the listening materials and key points.", + "Statement: \"Teaching Content\" must include more examples."], + "Teaching Time Allocation": [ + "Statement: \"Teaching Time Allocation\" must include how much time is allocated to each " + "part of the textbook content."], + "Teaching Methods and Strategies": [ + "Statement: \"Teaching Methods and Strategies\" must include teaching focus, difficulties, materials, " + "procedures, in detail." + ] + } + + # Teaching plan title + PROMPT_TITLE_TEMPLATE = """ + Do not refer to the context of the previous conversation records, start the conversation anew.\n\n + Formation: {formation}\n\n + {statements}\n + Constraint: Writing in {language}.\n + Answer options: Encloses the lesson title with "[TEACHING_PLAN_BEGIN]" and "[TEACHING_PLAN_END]" tags.\n + [LESSON_BEGIN]\n + {lesson}\n + [LESSON_END] + """ + + # Teaching plan parts: + PROMPT_TEMPLATE = """ + Do not refer to the context of the previous conversation records, start the conversation anew.\n\n + Formation: {formation}\n\n + Capacity and role: {role}\n + Statement: Write the "{topic}" part of teaching plan, WITHOUT ANY content unrelated to "{topic}"!!\n + {statements}\n + Answer options: Enclose the teaching plan content with "[TEACHING_PLAN_BEGIN]" and "[TEACHING_PLAN_END]" tags.\n + Answer options: Using proper markdown format from second-level header format.\n + Constraint: Writing in {language}.\n + [LESSON_BEGIN]\n + {lesson}\n + [LESSON_END] + """ + + DATA_BEGIN_TAG = "[TEACHING_PLAN_BEGIN]" + DATA_END_TAG = "[TEACHING_PLAN_END]" diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index f6499c643..ba5a655d3 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -4,12 +4,13 @@ @Time : 2023/5/5 23:08 @Author : alexanderwu @File : openai.py +@Modified By: mashenquan, 2023-07-27, + try except. """ import asyncio import time from functools import wraps from typing import NamedTuple - +import traceback import openai from metagpt.config import CONFIG @@ -30,7 +31,9 @@ def retry(max_retries): for i in range(max_retries): try: return await f(*args, **kwargs) - except Exception: + except Exception as e: + error_str = traceback.format_exc() + logger.warning(f"Exception occurred: {str(e)}, stack:{error_str}. Retrying...") if i == max_retries - 1: raise await asyncio.sleep(2 ** i) @@ -148,10 +151,15 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): self.rpm = int(config.get("RPM", 10)) async def _achat_completion_stream(self, messages: list[dict]) -> str: - response = await openai.ChatCompletion.acreate( - **self._cons_kwargs(messages), - stream=True - ) + try: + response = await openai.ChatCompletion.acreate( + **self._cons_kwargs(messages), + stream=True + ) + except Exception as e: + error_str = traceback.format_exc() + logger.error(f"Exception:{e}, stack:{error_str}") + raise e # create variables to collect the stream of chunks collected_chunks = [] diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 1681586cc..3e18257ed 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -4,9 +4,11 @@ @Time : 2023/5/11 14:42 @Author : alexanderwu @File : role.py +@Modified By: mashenquan, 2023-07-27, :class:`Role` + properties. """ from __future__ import annotations +import traceback from typing import Iterable, Type from pydantic import BaseModel, Field @@ -92,13 +94,22 @@ class RoleContext(BaseModel): class Role: """角色/代理""" - def __init__(self, name="", profile="", goal="", constraints="", desc=""): + def __init__(self, name="", profile="", goal="", constraints="", desc="", *args, **kwargs): + # Enable parameter configurability + name = Role.format_value(name, kwargs) + profile = Role.format_value(profile, kwargs) + goal = Role.format_value(goal, kwargs) + constraints = Role.format_value(constraints, kwargs) + desc = Role.format_value(desc, kwargs) + + # Initialize self._llm = LLM() self._setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc) self._states = [] self._actions = [] self._role_id = str(self._setting) self._rc = RoleContext() + self._options = Role.supply_options(kwargs) def _reset(self): self._states = [] @@ -136,6 +147,26 @@ class Role: """获取角色描述(职位)""" return self._setting.profile + @property + def name(self): + """Return role `name`, read only""" + return self._setting.name + + @property + def desc(self): + """Return role `desc`, read only""" + return self._setting.desc + + @property + def goal(self): + """Return role `goal`, read only""" + return self._setting.goal + + @property + def constraints(self): + """Return role `constraints`, read only""" + return self._setting.constraints + def _get_prefix(self): """获取角色前缀""" if self._setting.desc: @@ -164,7 +195,8 @@ class Role: # history=self.history) logger.info(f"{self._setting}: ready to {self._rc.todo}") - response = await self._rc.todo.run(self._rc.important_memory) + requirement = self._rc.important_memory + response = await self._rc.todo.run(requirement) # logger.info(response) if isinstance(response, ActionOutput): msg = Message(content=response.content, instruct_content=response.instruct_content, @@ -238,3 +270,33 @@ class Role: # 将回复发布到环境,等待下一个订阅者处理 self._publish_message(rsp) return rsp + + @staticmethod + def supply_options(options): + """Supply missing options""" + ret = Role.__DEFAULT_OPTIONS__.copy() + if not options: + return ret + ret.update(options) + return ret + + @staticmethod + def format_value(value, options): + """Fill parameters inside `value` with `options`. + """ + if "{" not in value: + return value + + options = Role.supply_options(options) + try: + return value.format(**options) + except KeyError as e: + logger.warning(f"Parameter is missing:{e}") + for k, v in options.items(): + value = value.replace("{" + f"{k}" + "}", v) + return value + + __DEFAULT_OPTIONS__ = { + "teaching_language": "English", + "language": "Chinese" + } \ No newline at end of file diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py new file mode 100644 index 000000000..a007926be --- /dev/null +++ b/metagpt/roles/teacher.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/23 17:25 +@Author : mashenquan +@File : teacher.py +""" +from pathlib import Path + +import aiofiles + +from metagpt.actions.write_teaching_plan import WriteTeachingPlanPart, TeachingPlanRequirement +from metagpt.roles import Role +from metagpt.schema import Message +from metagpt.logs import logger +import re + + +class Teacher(Role): + """Support configurable teacher roles, + with native and teaching languages being replaceable through configurations.""" + def __init__(self, name='Lily', profile='{teaching_language} Teacher', + goal='writing a {language} teaching plan part by part', + constraints='writing in {language}', desc="", *args, **kwargs): + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs) + actions = [] + for topic in WriteTeachingPlanPart.TOPICS: + act = WriteTeachingPlanPart(topic=topic, llm=self._llm) + actions.append(act) + self._init_actions(actions) + self._watch({TeachingPlanRequirement}) + + async def _think(self) -> None: + """Everything will be done part by part.""" + 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 _react(self) -> Message: + ret = Message(content="") + while True: + await self._think() + if self._rc.todo is None: + break + logger.debug(f"{self._setting}: {self._rc.state=}, will do {self._rc.todo}") + msg = await self._act() + if ret.content != '': + ret.content += "\n\n\n" + ret.content += msg.content + logger.info(ret.content) + await self.save(ret.content) + return ret + + async def save(self, content): + """Save teaching plan""" + filename = Teacher.new_file_name(self.course_title) + pathname = Path(__file__).resolve().parent.parent.parent / "output" + pathname.mkdir(exist_ok=True) + pathname = pathname / filename + try: + async with aiofiles.open(str(pathname), mode='w', encoding='utf-8') as writer: + await writer.write(content) + except Exception as e: + logger.error(f'Save failed:{e}') + logger.info(f"Save to:{pathname}") + + @staticmethod + def new_file_name(lesson_title, ext=".md"): + """Create a related file name based on `lesson_title` and `ext`.""" + # 定义需要替换的特殊字符 + illegal_chars = r'[#@$%!*&\\/:*?"<>|\n\t \']' + # 将特殊字符替换为下划线 + filename = re.sub(illegal_chars, '_', lesson_title) + ext + return re.sub(r'_+', '_', filename) + + @property + def course_title(self): + """Return course title of teaching plan""" + default_title = "teaching_plan" + for act in self._actions: + if act.topic != WriteTeachingPlanPart.COURSE_TITLE: + continue + if act.rsp is None: + return default_title + title = act.rsp.lstrip("# \n") + if '\n' in title: + ix = title.index('\n') + title = title[0: ix] + return title + + return default_title diff --git a/metagpt/software_company.py b/metagpt/software_company.py index 8f173ebf3..10fb025d6 100644 --- a/metagpt/software_company.py +++ b/metagpt/software_company.py @@ -4,6 +4,7 @@ @Time : 2023/5/12 00:30 @Author : alexanderwu @File : software_company.py +@Modified By: mashenquan, 2023-07-27, Add `role` & `cause_by` parameters to `start_project()`. """ from pydantic import BaseModel, Field @@ -42,10 +43,10 @@ class SoftwareCompany(BaseModel): if CONFIG.total_cost > CONFIG.max_budget: raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}') - def start_project(self, idea): + def start_project(self, idea, role="BOSS", cause_by=BossRequirement): """Start a project from publishing boss requirement.""" self.idea = idea - self.environment.publish_message(Message(role="BOSS", content=idea, cause_by=BossRequirement)) + self.environment.publish_message(Message(role=role, content=idea, cause_by=cause_by)) def _save(self): logger.info(self.json()) diff --git a/requirements.txt b/requirements.txt index 32a436962..4d5856c20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,3 +35,4 @@ tqdm==4.64.0 anthropic==0.3.6 typing-inspect==0.8.0 typing_extensions==4.5.0 +aiofiles \ No newline at end of file diff --git a/startup.py b/startup.py index e062babb5..17f55fb0a 100644 --- a/startup.py +++ b/startup.py @@ -1,15 +1,23 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +@Modified By: mashenquan, 2023-07-27, +industry concept +""" + import asyncio - +from pathlib import Path +import aiofiles import fire - +from metagpt.logs import logger +from metagpt.actions.write_teaching_plan import TeachingPlanRequirement from metagpt.roles import Architect, Engineer, ProductManager, ProjectManager +from metagpt.roles.teacher import Teacher from metagpt.software_company import SoftwareCompany -async def startup(idea: str, investment: float = 3.0, n_round: int = 5, code_review: bool = False): - """Run a startup. Be a boss.""" +async def software_startup(investment: float = 3.0, n_round: int = 5, code_review: bool = False, *args, **kwargs): + """Run a startup. Be a boss in software industry.""" + idea = kwargs['idea'] # Your innovative idea, such as "Creating a snake game." company = SoftwareCompany() company.hire([ProductManager(), Architect(), @@ -20,16 +28,90 @@ async def startup(idea: str, investment: float = 3.0, n_round: int = 5, code_rev await company.run(n_round=n_round) -def main(idea: str, investment: float = 3.0, n_round: int = 5, code_review: bool = False): +async def education_startup(investment: float = 3.0, n_round: int = 5, code_review: bool = False, *args, **kwargs): + """Run a startup. Be a teacher in education industry.""" + + demo_lesson = """ + UNIT 1 Making New Friends + TOPIC 1 Welcome to China! + Section A + + 1a Listen and number the following names. + Jane Mari Kangkang Michael + Look, listen and understand. Then practice the conversation. + Work in groups. Introduce yourself using + I ’m ... Then practice 1a + with your own hometown or the following places. + + 1b Listen and number the following names + Jane Michael Maria Kangkang + 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places. + China the USA the UK Hong Kong Beijing + + 2a Look, listen and understand. Then practice the conversation + Hello! + Hello! + Hello! + Hello! Are you Maria? + No, I’m not. I’m Jane. + Oh, nice to meet you, Jane + Nice to meet you, too. + Hi, Maria! + Hi, Kangkang! + Welcome to China! + Thanks. + + 2b Work in groups. Make up a conversation with your own name and the + following structures. + A: Hello! / Good morning! / Hi! I’m ... Are you ... ? + B: ... + + 3a Listen, say and trace + Aa Bb Cc Dd Ee Ff Gg + + 3b Listen and number the following letters. Then circle the letters withthe same sound as Bb. + Aa Bb Cc Dd Ee Ff Gg + + 3c Match the big letters with the small ones. Then write them on the lines. + """ + + lesson = "" + lesson_file = kwargs.get('lesson_file') + if lesson_file is not None and Path(lesson_file).exists(): + async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: + lesson = await reader.read() + logger.info(f"Course content: {lesson}") + if not lesson: + logger.info("No course content provided, using the demo course.") + lesson = demo_lesson + + company = SoftwareCompany() + company.hire([Teacher(*args, **kwargs)]) + company.invest(investment) + company.start_project(lesson, role="Teacher", cause_by=TeachingPlanRequirement) + await company.run(n_round=1) + + +def main(investment: float = 3.0, n_round: int = 5, code_review: bool = False, *args, **kwargs): """ We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. - :param idea: Your innovative idea, such as "Creating a snake game." :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. :param n_round: :param code_review: Whether to use code review. + :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` + :param kwargs: Parameters passed in format: `python your_script.py a--param1=value1 --param2=value2` :return: """ - asyncio.run(startup(idea, investment, n_round, code_review)) + industry = kwargs.get("industry", "software") + industries = { + "software": software_startup, + "education": education_startup, + } + startup = industries.get(industry) + if startup is None: + print(f"Available industries:{list(industries.keys())}") + return + asyncio.run(startup(investment, n_round, code_review, *args, **kwargs)) if __name__ == '__main__': diff --git a/tests/metagpt/roles/test_teacher.py b/tests/metagpt/roles/test_teacher.py new file mode 100644 index 000000000..0dddff3ac --- /dev/null +++ b/tests/metagpt/roles/test_teacher.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/23 17:25 +@Author : mashenquan +@File : teacher.py +""" + +from typing import Dict, Optional +from pydantic import BaseModel + +from metagpt.roles.teacher import Teacher + + +def test_init(): + class Inputs(BaseModel): + name: str + profile: str + goal: str + constraints: str + desc: str + options: Optional[Dict] = None + expect_name: str + expect_profile: str + expect_goal: str + expect_constraints: str + expect_desc: str + + inputs = [ + { + "name": "Lily{language}", + "expect_name": "LilyCN", + "profile": "X {teaching_language}", + "expect_profile": "X EN", + "goal": "Do {something_big}, {language}", + "expect_goal": "Do sleep, CN", + "constraints": "Do in {key1}, {language}", + "expect_constraints": "Do in HaHa, CN", + "options": {"language": "CN", "key1": "HaHa", "something_big": "sleep", "teaching_language": "EN"}, + "desc": "aaa{language}", + "expect_desc": "aaaCN" + }, + { + "name": "Lily{language}", + "expect_name": "LilyChinese", + "profile": "X {teaching_language}", + "expect_profile": "X English", + "goal": "Do {something_big}, {language}", + "expect_goal": "Do {something_big}, Chinese", + "constraints": "Do in {key1}, {language}", + "expect_constraints": "Do in {key1}, Chinese", + "desc": "aaa{language}", + "expect_desc": "aaaChinese" + }, + ] + + for i in inputs: + seed = Inputs(**i) + teacher = Teacher(name=seed.name, profile=seed.profile, goal=seed.goal, constraints=seed.constraints, + desc=seed.desc, options=seed.options) + assert teacher.name == seed.expect_name + assert teacher.desc == seed.expect_desc + assert teacher.profile == seed.expect_profile + assert teacher.goal == seed.expect_goal + assert teacher.constraints == seed.expect_constraints + assert teacher.course_title == "teaching_plan" + + +def test_new_file_name(): + class Inputs(BaseModel): + lesson_title: str + ext: str + expect: str + + inputs = [ + { + "lesson_title": "# @344\n12", + "ext": ".md", + "expect": "_344_12.md" + }, + { + "lesson_title": "1#@$%!*&\\/:*?\"<>|\n\t \'1", + "ext": ".cc", + "expect": "1_1.cc" + } + ] + for i in inputs: + seed = Inputs(**i) + result = Teacher.new_file_name(seed.lesson_title, seed.ext) + assert result == seed.expect + + +if __name__ == '__main__': + test_init() From 5725296b1cdf6f7df494811945e07e4fe797aeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 14:18:21 +0800 Subject: [PATCH 02/41] fixbug: unit test --- requirements-test.txt | 38 +++++++++++++++++++++++++++++ tests/metagpt/roles/test_teacher.py | 7 +++--- 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 000000000..4d5856c20 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,38 @@ +aiohttp==3.8.4 +#azure_storage==0.37.0 +channels==4.0.0 +# chromadb==0.3.22 +# Django==4.1.5 +# docx==0.2.4 +duckduckgo_search==2.9.4 +#faiss==1.5.3 +faiss_cpu==1.7.4 +fire==0.4.0 +# godot==0.1.1 +# google_api_python_client==2.93.0 +langchain==0.0.231 +loguru==0.6.0 +meilisearch==0.21.0 +numpy==1.24.3 +openai==0.27.8 +openpyxl +pandas==1.4.1 +pydantic==1.10.7 +#pygame==2.1.3 +#pymilvus==2.2.8 +pytest==7.2.2 +python_docx==0.8.11 +PyYAML==6.0 +# sentence_transformers==2.2.2 +setuptools==65.6.3 +tenacity==8.2.2 +tiktoken==0.3.3 +tqdm==4.64.0 +#unstructured[local-inference] +# playwright +# selenium>4 +# webdriver_manager<3.9 +anthropic==0.3.6 +typing-inspect==0.8.0 +typing_extensions==4.5.0 +aiofiles \ No newline at end of file diff --git a/tests/metagpt/roles/test_teacher.py b/tests/metagpt/roles/test_teacher.py index 0dddff3ac..10789f868 100644 --- a/tests/metagpt/roles/test_teacher.py +++ b/tests/metagpt/roles/test_teacher.py @@ -19,7 +19,7 @@ def test_init(): goal: str constraints: str desc: str - options: Optional[Dict] = None + kwargs: Optional[Dict] = None expect_name: str expect_profile: str expect_goal: str @@ -36,7 +36,7 @@ def test_init(): "expect_goal": "Do sleep, CN", "constraints": "Do in {key1}, {language}", "expect_constraints": "Do in HaHa, CN", - "options": {"language": "CN", "key1": "HaHa", "something_big": "sleep", "teaching_language": "EN"}, + "kwargs": {"language": "CN", "key1": "HaHa", "something_big": "sleep", "teaching_language": "EN"}, "desc": "aaa{language}", "expect_desc": "aaaCN" }, @@ -49,6 +49,7 @@ def test_init(): "expect_goal": "Do {something_big}, Chinese", "constraints": "Do in {key1}, {language}", "expect_constraints": "Do in {key1}, Chinese", + "kwargs": {}, "desc": "aaa{language}", "expect_desc": "aaaChinese" }, @@ -57,7 +58,7 @@ def test_init(): for i in inputs: seed = Inputs(**i) teacher = Teacher(name=seed.name, profile=seed.profile, goal=seed.goal, constraints=seed.constraints, - desc=seed.desc, options=seed.options) + desc=seed.desc, **seed.kwargs) assert teacher.name == seed.expect_name assert teacher.desc == seed.expect_desc assert teacher.profile == seed.expect_profile From 686ca2347817ee913d8e53f51846a4d988cdb899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 15:33:52 +0800 Subject: [PATCH 03/41] fixbug: startup parameters do not match --- requirements-test.txt | 17 ++++++++++------- startup.py | 15 +++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index 4d5856c20..7c03dddd9 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,7 @@ aiohttp==3.8.4 -#azure_storage==0.37.0 +azure-cognitiveservices-speech==1.30.0 channels==4.0.0 -# chromadb==0.3.22 +chromadb==0.3.22 # Django==4.1.5 # docx==0.2.4 duckduckgo_search==2.9.4 @@ -19,7 +19,7 @@ openpyxl pandas==1.4.1 pydantic==1.10.7 #pygame==2.1.3 -#pymilvus==2.2.8 +pymilvus==2.2.8 pytest==7.2.2 python_docx==0.8.11 PyYAML==6.0 @@ -29,10 +29,13 @@ tenacity==8.2.2 tiktoken==0.3.3 tqdm==4.64.0 #unstructured[local-inference] -# playwright -# selenium>4 -# webdriver_manager<3.9 +playwright +selenium>4 +webdriver_manager<3.9 anthropic==0.3.6 typing-inspect==0.8.0 typing_extensions==4.5.0 -aiofiles \ No newline at end of file +bs4 +aiofiles +pytest +pytest-asyncio \ No newline at end of file diff --git a/startup.py b/startup.py index 17f55fb0a..c05bbbbf0 100644 --- a/startup.py +++ b/startup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Modified By: mashenquan, 2023-07-27, +industry concept +@Modified By: mashenquan, 2023-07-27, + `industry` concept """ import asyncio @@ -15,9 +15,9 @@ from metagpt.roles.teacher import Teacher from metagpt.software_company import SoftwareCompany -async def software_startup(investment: float = 3.0, n_round: int = 5, code_review: bool = False, *args, **kwargs): +async def software_startup(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): """Run a startup. Be a boss in software industry.""" - idea = kwargs['idea'] # Your innovative idea, such as "Creating a snake game." + code_review = kwargs.get("code_review", False) # Whether to use code review. company = SoftwareCompany() company.hire([ProductManager(), Architect(), @@ -28,7 +28,7 @@ async def software_startup(investment: float = 3.0, n_round: int = 5, code_revie await company.run(n_round=n_round) -async def education_startup(investment: float = 3.0, n_round: int = 5, code_review: bool = False, *args, **kwargs): +async def education_startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, *args, **kwargs): """Run a startup. Be a teacher in education industry.""" demo_lesson = """ @@ -76,7 +76,6 @@ async def education_startup(investment: float = 3.0, n_round: int = 5, code_revi """ lesson = "" - lesson_file = kwargs.get('lesson_file') if lesson_file is not None and Path(lesson_file).exists(): async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: lesson = await reader.read() @@ -92,12 +91,12 @@ async def education_startup(investment: float = 3.0, n_round: int = 5, code_revi await company.run(n_round=1) -def main(investment: float = 3.0, n_round: int = 5, code_review: bool = False, *args, **kwargs): +def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): """ We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. + :param idea: Your innovative idea for `software` industry, such as "Creating a snake game."; lesson filename for `education` industry. :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. :param n_round: - :param code_review: Whether to use code review. :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` :param kwargs: Parameters passed in format: `python your_script.py a--param1=value1 --param2=value2` :return: @@ -111,7 +110,7 @@ def main(investment: float = 3.0, n_round: int = 5, code_review: bool = False, * if startup is None: print(f"Available industries:{list(industries.keys())}") return - asyncio.run(startup(investment, n_round, code_review, *args, **kwargs)) + asyncio.run(startup(idea, investment, n_round, *args, **kwargs)) if __name__ == '__main__': From 255c56ca2658a3b2bcf76e8f68edc8b864a32572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:06:32 +0800 Subject: [PATCH 04/41] feat: +test --- .../actions/test_write_teaching_plan.py | 57 +++++++++++++++++++ tests/metagpt/roles/test_teacher.py | 1 + 2 files changed, 58 insertions(+) create mode 100644 tests/metagpt/actions/test_write_teaching_plan.py diff --git a/tests/metagpt/actions/test_write_teaching_plan.py b/tests/metagpt/actions/test_write_teaching_plan.py new file mode 100644 index 000000000..b47d6ab56 --- /dev/null +++ b/tests/metagpt/actions/test_write_teaching_plan.py @@ -0,0 +1,57 @@ +import asyncio +from typing import Optional +from pydantic import BaseModel +from langchain.llms.base import LLM + +from metagpt.actions.write_teaching_plan import WriteTeachingPlanPart +from metagpt.schema import Message + + +class MockWriteTeachingPlanPart(WriteTeachingPlanPart): + def __init__(self, name: str = '', context=None, llm: LLM = None, topic="", language="Chinese"): + super().__init__(name, context, llm, topic, language) + + async def _aask(self, prompt: str, system_msgs: Optional[list[str]] = None) -> str: + return f"{WriteTeachingPlanPart.DATA_BEGIN_TAG}\nprompt\n{WriteTeachingPlanPart.DATA_END_TAG}" + + +async def mock_write_teaching_plan_part(): + class Inputs(BaseModel): + input: str + name: str + topic: str + language: str + + inputs = [ + { + "input": "AABBCC", + "name": "A", + "topic": "B", + "language": "C" + }, + { + "input": "DDEEFFF", + "name": "A1", + "topic": "B1", + "language": "C1" + } + ] + + for i in inputs: + seed = Inputs(**i) + act = MockWriteTeachingPlanPart(name=seed.name, topic=seed.topic, language=seed.language) + await act.run([Message(content="")]) + assert act.topic == seed.topic + assert str(act) == seed.topic + assert act.name == seed.name + assert act.rsp == "prompt" + + +def test_suite(): + loop = asyncio.get_event_loop() + task = loop.create_task(mock_write_teaching_plan_part()) + loop.run_until_complete(task) + + +if __name__ == '__main__': + test_suite() diff --git a/tests/metagpt/roles/test_teacher.py b/tests/metagpt/roles/test_teacher.py index 10789f868..3af053338 100644 --- a/tests/metagpt/roles/test_teacher.py +++ b/tests/metagpt/roles/test_teacher.py @@ -93,3 +93,4 @@ def test_new_file_name(): if __name__ == '__main__': test_init() + test_new_file_name() From 2e6f88d3e4ab556f79626bf91367de7cd945de72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:10:54 +0800 Subject: [PATCH 05/41] feat: + notation --- metagpt/actions/write_teaching_plan.py | 2 +- metagpt/roles/teacher.py | 2 +- tests/metagpt/actions/test_write_teaching_plan.py | 8 ++++++++ tests/metagpt/roles/test_teacher.py | 4 ++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 1f0167df3..76c72651d 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/5/11 14:43 +@Time : 2023/7/27 @Author : mashenquan @File : write_teaching_plan.py """ diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index a007926be..acaa3860f 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/5/23 17:25 +@Time : 2023/7/27 @Author : mashenquan @File : teacher.py """ diff --git a/tests/metagpt/actions/test_write_teaching_plan.py b/tests/metagpt/actions/test_write_teaching_plan.py index b47d6ab56..2e34491fb 100644 --- a/tests/metagpt/actions/test_write_teaching_plan.py +++ b/tests/metagpt/actions/test_write_teaching_plan.py @@ -1,3 +1,11 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/7/28 17:25 +@Author : mashenquan +@File : test_write_teaching_plan.py +""" + import asyncio from typing import Optional from pydantic import BaseModel diff --git a/tests/metagpt/roles/test_teacher.py b/tests/metagpt/roles/test_teacher.py index 3af053338..5faa43455 100644 --- a/tests/metagpt/roles/test_teacher.py +++ b/tests/metagpt/roles/test_teacher.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Time : 2023/5/23 17:25 +@Time : 2023/7/27 13:25 @Author : mashenquan -@File : teacher.py +@File : test_teacher.py """ from typing import Dict, Optional From 34e5658009016cefd0083c0797ef6d3f42b68fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:14:37 +0800 Subject: [PATCH 06/41] feat: + notation --- metagpt/actions/write_teaching_plan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 76c72651d..0778b86b4 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -68,11 +68,11 @@ class WriteTeachingPlanPart(Action): self.rsp = "# " + self.rsp def __str__(self): - """str()时返回`topic`""" + """Return `topic` value when str()""" return self.topic def __repr__(self): - """调试时返回`topic`""" + """Show `topic` value when debug""" return self.topic FORMATION = """ From d7c1d9797f82d80015f7ec3e548466c9b25b938c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:22:28 +0800 Subject: [PATCH 07/41] fixbug: prompt format error --- metagpt/actions/write_teaching_plan.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 0778b86b4..370c70040 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -76,11 +76,11 @@ class WriteTeachingPlanPart(Action): return self.topic FORMATION = """ - "\tCapacity and role" defines the role you are currently playing; - "\t[LESSON_BEGIN]" and "[LESSON_END]" tags enclose the content of textbook; - "\tStatement" defines the work detail you need to complete at this stage; - "\tAnswer options" defines the format requirements for your responses; - "\tConstraint" defines the conditions that your responses must comply with. + \t\"Capacity and role\" defines the role you are currently playing; + \t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook; + \t\"Statement\" defines the work detail you need to complete at this stage; + \t\"Answer options\" defines the format requirements for your responses; + \t\"Constraint\" defines the conditions that your responses must comply with. """ COURSE_TITLE = "Title" TOPICS = [COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", From d79fe56a38596385a6ea5aafdb5752942bbebedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:34:30 +0800 Subject: [PATCH 08/41] fixbug: prompt format error --- metagpt/actions/write_teaching_plan.py | 60 +++++++++---------- .../actions/test_write_teaching_plan.py | 4 +- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 370c70040..4c36983ff 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -75,13 +75,12 @@ class WriteTeachingPlanPart(Action): """Show `topic` value when debug""" return self.topic - FORMATION = """ - \t\"Capacity and role\" defines the role you are currently playing; - \t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook; - \t\"Statement\" defines the work detail you need to complete at this stage; - \t\"Answer options\" defines the format requirements for your responses; - \t\"Constraint\" defines the conditions that your responses must comply with. - """ + FORMATION = "\"Capacity and role\" defines the role you are currently playing;\n" \ + "\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n" \ + "\t\"Statement\" defines the work detail you need to complete at this stage;\n" \ + "\t\"Answer options\" defines the format requirements for your responses;\n" \ + "\t\"Constraint\" defines the conditions that your responses must comply with." + COURSE_TITLE = "Title" TOPICS = [COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", "Teaching Methods and Strategies", "Learning Activities", @@ -104,31 +103,32 @@ class WriteTeachingPlanPart(Action): } # Teaching plan title - PROMPT_TITLE_TEMPLATE = """ - Do not refer to the context of the previous conversation records, start the conversation anew.\n\n - Formation: {formation}\n\n - {statements}\n - Constraint: Writing in {language}.\n - Answer options: Encloses the lesson title with "[TEACHING_PLAN_BEGIN]" and "[TEACHING_PLAN_END]" tags.\n - [LESSON_BEGIN]\n - {lesson}\n - [LESSON_END] - """ + PROMPT_TITLE_TEMPLATE = "Do not refer to the context of the previous conversation records, " \ + "start the conversation anew.\n\n" \ + "Formation: {formation}\n\n" \ + "{statements}\n" \ + "Constraint: Writing in {language}.\n" \ + "Answer options: Encloses the lesson title with \"[TEACHING_PLAN_BEGIN]\" " \ + "and \"[TEACHING_PLAN_END]\" tags.\n" \ + "[LESSON_BEGIN]\n" \ + "{lesson}\n" \ + "[LESSON_END]" # Teaching plan parts: - PROMPT_TEMPLATE = """ - Do not refer to the context of the previous conversation records, start the conversation anew.\n\n - Formation: {formation}\n\n - Capacity and role: {role}\n - Statement: Write the "{topic}" part of teaching plan, WITHOUT ANY content unrelated to "{topic}"!!\n - {statements}\n - Answer options: Enclose the teaching plan content with "[TEACHING_PLAN_BEGIN]" and "[TEACHING_PLAN_END]" tags.\n - Answer options: Using proper markdown format from second-level header format.\n - Constraint: Writing in {language}.\n - [LESSON_BEGIN]\n - {lesson}\n - [LESSON_END] - """ + PROMPT_TEMPLATE = "Do not refer to the context of the previous conversation records, " \ + "start the conversation anew.\n\n" \ + "Formation: {formation}\n\n" \ + "Capacity and role: {role}\n" \ + "Statement: Write the \"{topic}\" part of teaching plan, " \ + "WITHOUT ANY content unrelated to \"{topic}\"!!\n" \ + "{statements}\n" \ + "Answer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" " \ + "and \"[TEACHING_PLAN_END]\" tags.\n" \ + "Answer options: Using proper markdown format from second-level header format.\n" \ + "Constraint: Writing in {language}.\n" \ + "[LESSON_BEGIN]\n" \ + "{lesson}\n" \ + "[LESSON_END]" DATA_BEGIN_TAG = "[TEACHING_PLAN_BEGIN]" DATA_END_TAG = "[TEACHING_PLAN_END]" diff --git a/tests/metagpt/actions/test_write_teaching_plan.py b/tests/metagpt/actions/test_write_teaching_plan.py index 2e34491fb..299a89639 100644 --- a/tests/metagpt/actions/test_write_teaching_plan.py +++ b/tests/metagpt/actions/test_write_teaching_plan.py @@ -34,7 +34,7 @@ async def mock_write_teaching_plan_part(): { "input": "AABBCC", "name": "A", - "topic": "B", + "topic": WriteTeachingPlanPart.COURSE_TITLE, "language": "C" }, { @@ -52,7 +52,7 @@ async def mock_write_teaching_plan_part(): assert act.topic == seed.topic assert str(act) == seed.topic assert act.name == seed.name - assert act.rsp == "prompt" + assert act.rsp == "# prompt" if seed.topic == WriteTeachingPlanPart.COURSE_TITLE else "prompt" def test_suite(): From b95a01079c4a0cc18119ef196f5f0124556fbdc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:42:02 +0800 Subject: [PATCH 09/41] feat: + notation --- metagpt/roles/teacher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index acaa3860f..fede9f74a 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -72,9 +72,9 @@ class Teacher(Role): @staticmethod def new_file_name(lesson_title, ext=".md"): """Create a related file name based on `lesson_title` and `ext`.""" - # 定义需要替换的特殊字符 + # Define the special characters that need to be replaced. illegal_chars = r'[#@$%!*&\\/:*?"<>|\n\t \']' - # 将特殊字符替换为下划线 + # Replace the special characters with underscores. filename = re.sub(illegal_chars, '_', lesson_title) + ext return re.sub(r'_+', '_', filename) From 16bad64649cbdc3b498376981dd0039a63e0eff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 28 Jul 2023 16:44:30 +0800 Subject: [PATCH 10/41] feat: + notation --- startup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startup.py b/startup.py index c05bbbbf0..1fe4a067a 100644 --- a/startup.py +++ b/startup.py @@ -98,7 +98,7 @@ def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. :param n_round: :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` - :param kwargs: Parameters passed in format: `python your_script.py a--param1=value1 --param2=value2` + :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` :return: """ industry = kwargs.get("industry", "software") From 7cab03942ebdc9a7fbfcc34f31d0bf8d780d6fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 31 Jul 2023 15:44:12 +0800 Subject: [PATCH 11/41] =?UTF-8?q?feat:=20+=E7=BB=83=E4=B9=A0=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/write_teaching_plan.py | 57 +++++++++++++++++++++++++- metagpt/roles/role.py | 2 +- startup.py | 2 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 4c36983ff..66c370bc9 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -39,7 +39,12 @@ class WriteTeachingPlanPart(Action): if len(args) < 1 or len(args[0]) < 1 or not isinstance(args[0][0], Message): raise ValueError("Invalid args, a tuple of List[Message] is expected") - statements = self.TOPIC_STATEMENTS.get(self.topic, []) + statement_patterns = self.TOPIC_STATEMENTS.get(self.topic, []) + statements = [] + from metagpt.roles import Role + for p in statement_patterns: + s = Role.format_value(p, kwargs) + statements.append(s) formatter = self.PROMPT_TITLE_TEMPLATE if self.topic == self.COURSE_TITLE else self.PROMPT_TEMPLATE prompt = formatter.format(formation=self.FORMATION, role=self.prefix, @@ -84,7 +89,9 @@ class WriteTeachingPlanPart(Action): COURSE_TITLE = "Title" TOPICS = [COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", "Teaching Methods and Strategies", "Learning Activities", - "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement"] + "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement", + "Vocabulary Practice", "Grammar Practice", "Reading Comprehension", "Listening Practice", + "Writing Practice", "Speaking Practice", "Translation Practice", "Listening and Speaking Activities"] TOPIC_STATEMENTS = { COURSE_TITLE: ["Statement: Find and return the title of the lesson only in markdown first-level header format, " @@ -99,6 +106,52 @@ class WriteTeachingPlanPart(Action): "Teaching Methods and Strategies": [ "Statement: \"Teaching Methods and Strategies\" must include teaching focus, difficulties, materials, " "procedures, in detail." + ], + "Vocabulary Practice": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create vocabulary practice exercises. The exercises should be in either {language} with " + "{teaching_language} answers or {teaching_language} with {language} answers. The key-related vocabulary " + "and phrases in the textbook content must all be included in the exercises." + ], + "Grammar Practice": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create grammar practice exercises. "], + "Reading Comprehension": [ + "Statement: Based on the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create {teaching_language} reading comprehension exercises. ", + "Statement: Prohibit the use of words that are not within the scope of the \"[LESSON_BEGIN]\" " + "and \"[LESSON_END]\" tags.", + "Statement: Prohibit copy the content of the \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", + "Answer options: Write the story content in {teaching_language}." + ], + "Listening Practice": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create listening practice exercises. Each exercise should include the audio content and the " + "question-and-answer part." + ], + "Writing Practice": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create writing practice exercises.", + #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", + "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + ], + "Speaking Practice": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create speaking practice exercises.", + #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", + "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + ], + "Translation Practice": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create Translation practice exercises.", + #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", + "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + ], + "Listening and Speaking Activities": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create listening and speaking activities exercises.", + #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", + "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." ] } diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 3e18257ed..47aa90197 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -196,7 +196,7 @@ class Role: logger.info(f"{self._setting}: ready to {self._rc.todo}") requirement = self._rc.important_memory - response = await self._rc.todo.run(requirement) + response = await self._rc.todo.run(requirement, **self._options) # logger.info(response) if isinstance(response, ActionOutput): msg = Message(content=response.content, instruct_content=response.instruct_content, diff --git a/startup.py b/startup.py index 1fe4a067a..ee8cd3b6e 100644 --- a/startup.py +++ b/startup.py @@ -69,7 +69,7 @@ async def education_startup(lesson_file: str, investment: float = 3.0, n_round: 3a Listen, say and trace Aa Bb Cc Dd Ee Ff Gg - 3b Listen and number the following letters. Then circle the letters withthe same sound as Bb. + 3b Listen and number the following letters. Then circle the letters with the same sound as Bb. Aa Bb Cc Dd Ee Ff Gg 3c Match the big letters with the small ones. Then write them on the lines. From 8b7eddad86c47e76b200c3ed4cbe1c2377a3767f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 31 Jul 2023 15:57:06 +0800 Subject: [PATCH 12/41] =?UTF-8?q?feat:=20+=E7=BB=83=E4=B9=A0=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/write_teaching_plan.py | 33 +++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 66c370bc9..09b45634c 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -87,7 +87,8 @@ class WriteTeachingPlanPart(Action): "\t\"Constraint\" defines the conditions that your responses must comply with." COURSE_TITLE = "Title" - TOPICS = [COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", + TOPICS = [ + COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", "Teaching Methods and Strategies", "Learning Activities", "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement", "Vocabulary Practice", "Grammar Practice", "Reading Comprehension", "Listening Practice", @@ -117,41 +118,41 @@ class WriteTeachingPlanPart(Action): "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " "create grammar practice exercises. "], "Reading Comprehension": [ - "Statement: Based on the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create {teaching_language} reading comprehension exercises. ", - "Statement: Prohibit the use of words that are not within the scope of the \"[LESSON_BEGIN]\" " - "and \"[LESSON_END]\" tags.", - "Statement: Prohibit copy the content of the \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - "Answer options: Write the story content in {teaching_language}." + "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create {teaching_language} reading comprehension exercises. " + # "Statement: Prohibit the use of words that are not within the scope of the \"[LESSON_BEGIN]\" " + # "and \"[LESSON_END]\" tags.", + # "Statement: Prohibit copy the content of the \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", + # "Answer options: Write the story content in {teaching_language}." ], "Listening Practice": [ - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " "create listening practice exercises. Each exercise should include the audio content and the " "question-and-answer part." ], "Writing Practice": [ - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " "create writing practice exercises.", #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." ], "Speaking Practice": [ - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " "create speaking practice exercises.", #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." ], "Translation Practice": [ - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " "create Translation practice exercises.", #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." ], "Listening and Speaking Activities": [ - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " "create listening and speaking activities exercises.", #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - "Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." + #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." ] } From 444b609e38c931fa18fcf04d35b7ed4016fb46d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 31 Jul 2023 17:27:09 +0800 Subject: [PATCH 13/41] =?UTF-8?q?feat:=20=E5=88=A0=E6=8E=89=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E7=9A=84part?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/write_teaching_plan.py | 94 ++++++++++++++++++++++++++ metagpt/actions/write_teaching_plan.py | 53 +++------------ 2 files changed, 102 insertions(+), 45 deletions(-) create mode 100644 examples/write_teaching_plan.py diff --git a/examples/write_teaching_plan.py b/examples/write_teaching_plan.py new file mode 100644 index 000000000..ec8ad8948 --- /dev/null +++ b/examples/write_teaching_plan.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Modified By: mashenquan, 2023-07-27, + `industry` concept +""" + +import asyncio +from pathlib import Path +import aiofiles +import fire +from metagpt.logs import logger +from metagpt.actions.write_teaching_plan import TeachingPlanRequirement +from metagpt.roles.teacher import Teacher +from metagpt.software_company import SoftwareCompany + + +async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, *args, **kwargs): + """Run a startup. Be a teacher in education industry.""" + + demo_lesson = """ + UNIT 1 Making New Friends + TOPIC 1 Welcome to China! + Section A + + 1a Listen and number the following names. + Jane Mari Kangkang Michael + Look, listen and understand. Then practice the conversation. + Work in groups. Introduce yourself using + I ’m ... Then practice 1a + with your own hometown or the following places. + + 1b Listen and number the following names + Jane Michael Maria Kangkang + 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places. + China the USA the UK Hong Kong Beijing + + 2a Look, listen and understand. Then practice the conversation + Hello! + Hello! + Hello! + Hello! Are you Maria? + No, I’m not. I’m Jane. + Oh, nice to meet you, Jane + Nice to meet you, too. + Hi, Maria! + Hi, Kangkang! + Welcome to China! + Thanks. + + 2b Work in groups. Make up a conversation with your own name and the + following structures. + A: Hello! / Good morning! / Hi! I’m ... Are you ... ? + B: ... + + 3a Listen, say and trace + Aa Bb Cc Dd Ee Ff Gg + + 3b Listen and number the following letters. Then circle the letters with the same sound as Bb. + Aa Bb Cc Dd Ee Ff Gg + + 3c Match the big letters with the small ones. Then write them on the lines. + """ + + lesson = "" + if lesson_file is not None and Path(lesson_file).exists(): + async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: + lesson = await reader.read() + logger.info(f"Course content: {lesson}") + if not lesson: + logger.info("No course content provided, using the demo course.") + lesson = demo_lesson + + company = SoftwareCompany() + company.hire([Teacher(*args, **kwargs)]) + company.invest(investment) + company.start_project(lesson, role="Teacher", cause_by=TeachingPlanRequirement) + await company.run(n_round=1) + + +def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): + """ + We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. + :param idea: Your innovative idea for `software` industry, such as "Creating a snake game."; lesson filename for `education` industry. + :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. + :param n_round: + :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` + :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` + :return: + """ + asyncio.run(startup(idea, investment, n_round, *args, **kwargs)) + + +if __name__ == '__main__': + fire.Fire(main) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 09b45634c..bd6e96956 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -89,10 +89,10 @@ class WriteTeachingPlanPart(Action): COURSE_TITLE = "Title" TOPICS = [ COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", - "Teaching Methods and Strategies", "Learning Activities", - "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement", - "Vocabulary Practice", "Grammar Practice", "Reading Comprehension", "Listening Practice", - "Writing Practice", "Speaking Practice", "Translation Practice", "Listening and Speaking Activities"] + "Teaching Methods and Strategies", "Learning Activities", + "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement", + "Vocabulary Cloze", "Grammar Questions" + ] TOPIC_STATEMENTS = { COURSE_TITLE: ["Statement: Find and return the title of the lesson only in markdown first-level header format, " @@ -108,52 +108,15 @@ class WriteTeachingPlanPart(Action): "Statement: \"Teaching Methods and Strategies\" must include teaching focus, difficulties, materials, " "procedures, in detail." ], - "Vocabulary Practice": [ + "Vocabulary Cloze": [ "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create vocabulary practice exercises. The exercises should be in either {language} with " + "create vocabulary cloze. The cloze should be in either {language} with " "{teaching_language} answers or {teaching_language} with {language} answers. The key-related vocabulary " "and phrases in the textbook content must all be included in the exercises." ], - "Grammar Practice": [ + "Grammar Questions": [ "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create grammar practice exercises. "], - "Reading Comprehension": [ - "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create {teaching_language} reading comprehension exercises. " - # "Statement: Prohibit the use of words that are not within the scope of the \"[LESSON_BEGIN]\" " - # "and \"[LESSON_END]\" tags.", - # "Statement: Prohibit copy the content of the \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - # "Answer options: Write the story content in {teaching_language}." - ], - "Listening Practice": [ - "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create listening practice exercises. Each exercise should include the audio content and the " - "question-and-answer part." - ], - "Writing Practice": [ - "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create writing practice exercises.", - #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." - ], - "Speaking Practice": [ - "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create speaking practice exercises.", - #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." - ], - "Translation Practice": [ - "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create Translation practice exercises.", - #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." - ], - "Listening and Speaking Activities": [ - "Statement: Using the vocabulary of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create listening and speaking activities exercises.", - #"Statement: Prohibit using content not related to \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags.", - #"Statement: Prohibit copying the content enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags." - ] + "create grammar questions. "] } # Teaching plan title From 3b0f76a1cd1ffada96f5e47ab6c91e7618b5efaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 31 Jul 2023 17:48:06 +0800 Subject: [PATCH 14/41] =?UTF-8?q?fixbug:=20=E7=BB=9F=E4=B8=80=E4=BA=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/design_api.py | 3 ++- metagpt/actions/project_management.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 1447eacc3..55213a4b0 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -135,7 +135,8 @@ class WriteDesign(Action): self._save_prd(docs_path, resources_path, context[-1].content) self._save_system_design(docs_path, resources_path, content) - async def run(self, context): + async def run(self, *args, **kwargs): + context = args[0] 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) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 89c59dcda..394f279e8 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -115,7 +115,8 @@ class WriteTasks(Action): requirements_path = WORKSPACE_ROOT / ws_name / 'requirements.txt' requirements_path.write_text(rsp.instruct_content.dict().get("Required Python third-party packages").strip('"\n')) - async def run(self, context): + async def run(self, *args, **kwargs): + context = args[0] prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING) self._save(context, rsp) From 4379d360228e6ded6900ca855658daca02c80172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 31 Jul 2023 17:48:41 +0800 Subject: [PATCH 15/41] =?UTF-8?q?fixbug:=20=E7=BB=9F=E4=B8=80=E4=BA=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- startup.py | 97 +++++------------------------------------------------- 1 file changed, 8 insertions(+), 89 deletions(-) diff --git a/startup.py b/startup.py index ee8cd3b6e..e062babb5 100644 --- a/startup.py +++ b/startup.py @@ -1,23 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -""" -@Modified By: mashenquan, 2023-07-27, + `industry` concept -""" - import asyncio -from pathlib import Path -import aiofiles + import fire -from metagpt.logs import logger -from metagpt.actions.write_teaching_plan import TeachingPlanRequirement + from metagpt.roles import Architect, Engineer, ProductManager, ProjectManager -from metagpt.roles.teacher import Teacher from metagpt.software_company import SoftwareCompany -async def software_startup(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): - """Run a startup. Be a boss in software industry.""" - code_review = kwargs.get("code_review", False) # Whether to use code review. +async def startup(idea: str, investment: float = 3.0, n_round: int = 5, code_review: bool = False): + """Run a startup. Be a boss.""" company = SoftwareCompany() company.hire([ProductManager(), Architect(), @@ -28,89 +20,16 @@ async def software_startup(idea: str, investment: float = 3.0, n_round: int = 5, await company.run(n_round=n_round) -async def education_startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, *args, **kwargs): - """Run a startup. Be a teacher in education industry.""" - - demo_lesson = """ - UNIT 1 Making New Friends - TOPIC 1 Welcome to China! - Section A - - 1a Listen and number the following names. - Jane Mari Kangkang Michael - Look, listen and understand. Then practice the conversation. - Work in groups. Introduce yourself using - I ’m ... Then practice 1a - with your own hometown or the following places. - - 1b Listen and number the following names - Jane Michael Maria Kangkang - 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places. - China the USA the UK Hong Kong Beijing - - 2a Look, listen and understand. Then practice the conversation - Hello! - Hello! - Hello! - Hello! Are you Maria? - No, I’m not. I’m Jane. - Oh, nice to meet you, Jane - Nice to meet you, too. - Hi, Maria! - Hi, Kangkang! - Welcome to China! - Thanks. - - 2b Work in groups. Make up a conversation with your own name and the - following structures. - A: Hello! / Good morning! / Hi! I’m ... Are you ... ? - B: ... - - 3a Listen, say and trace - Aa Bb Cc Dd Ee Ff Gg - - 3b Listen and number the following letters. Then circle the letters with the same sound as Bb. - Aa Bb Cc Dd Ee Ff Gg - - 3c Match the big letters with the small ones. Then write them on the lines. - """ - - lesson = "" - if lesson_file is not None and Path(lesson_file).exists(): - async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: - lesson = await reader.read() - logger.info(f"Course content: {lesson}") - if not lesson: - logger.info("No course content provided, using the demo course.") - lesson = demo_lesson - - company = SoftwareCompany() - company.hire([Teacher(*args, **kwargs)]) - company.invest(investment) - company.start_project(lesson, role="Teacher", cause_by=TeachingPlanRequirement) - await company.run(n_round=1) - - -def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): +def main(idea: str, investment: float = 3.0, n_round: int = 5, code_review: bool = False): """ We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. - :param idea: Your innovative idea for `software` industry, such as "Creating a snake game."; lesson filename for `education` industry. + :param idea: Your innovative idea, such as "Creating a snake game." :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. :param n_round: - :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` - :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` + :param code_review: Whether to use code review. :return: """ - industry = kwargs.get("industry", "software") - industries = { - "software": software_startup, - "education": education_startup, - } - startup = industries.get(industry) - if startup is None: - print(f"Available industries:{list(industries.keys())}") - return - asyncio.run(startup(idea, investment, n_round, *args, **kwargs)) + asyncio.run(startup(idea, investment, n_round, code_review)) if __name__ == '__main__': From 85c7148b6235fb91a8d141b1d6be990e8999ced8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 10:04:21 +0800 Subject: [PATCH 16/41] feat: +param type --- metagpt/actions/write_teaching_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index bd6e96956..10fc2863f 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -21,7 +21,7 @@ class TeachingPlanRequirement(Action): class WriteTeachingPlanPart(Action): """Write Teaching Plan Part""" - def __init__(self, name: str = '', context=None, llm: LLM = None, topic="", language="Chinese"): + def __init__(self, name: str = "", context=None, llm: LLM = None, topic: str = "", language: str = "Chinese"): """ Args: From d415ca5dbc5a9622cb65b331d7ca87c224de57a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 10:48:26 +0800 Subject: [PATCH 17/41] fixbug: tests --- metagpt/actions/action.py | 1 + metagpt/actions/design_api.py | 3 +-- metagpt/actions/project_management.py | 3 +-- metagpt/actions/write_teaching_plan.py | 9 ++++----- metagpt/roles/teacher.py | 3 ++- tests/metagpt/actions/test_ui_design.py | 10 ++++------ tests/metagpt/actions/test_write_code.py | 3 ++- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index fa0d592a3..6b9ea626c 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -15,6 +15,7 @@ from metagpt.llm import LLM from metagpt.utils.common import OutputParser from metagpt.logs import logger + class Action(ABC): def __init__(self, name: str = '', context=None, llm: LLM = None): self.name: str = name diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 55213a4b0..1447eacc3 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -135,8 +135,7 @@ class WriteDesign(Action): self._save_prd(docs_path, resources_path, context[-1].content) self._save_system_design(docs_path, resources_path, content) - async def run(self, *args, **kwargs): - context = args[0] + 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) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 394f279e8..89c59dcda 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -115,8 +115,7 @@ class WriteTasks(Action): requirements_path = WORKSPACE_ROOT / ws_name / 'requirements.txt' requirements_path.write_text(rsp.instruct_content.dict().get("Required Python third-party packages").strip('"\n')) - async def run(self, *args, **kwargs): - context = args[0] + async def run(self, context): prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING) self._save(context, rsp) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 10fc2863f..e8fe110d8 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -5,7 +5,6 @@ @Author : mashenquan @File : write_teaching_plan.py """ -from langchain.llms.base import LLM from metagpt.logs import logger from metagpt.actions import Action from metagpt.schema import Message @@ -21,7 +20,7 @@ class TeachingPlanRequirement(Action): class WriteTeachingPlanPart(Action): """Write Teaching Plan Part""" - def __init__(self, name: str = "", context=None, llm: LLM = None, topic: str = "", language: str = "Chinese"): + def __init__(self, name: str = "", context=None, llm=None, topic: str = "", language: str = "Chinese"): """ Args: @@ -35,8 +34,8 @@ class WriteTeachingPlanPart(Action): self.language = language self.rsp = None - async def run(self, *args, **kwargs): - if len(args) < 1 or len(args[0]) < 1 or not isinstance(args[0][0], Message): + async def run(self, messages, *args, **kwargs): + if len(messages) < 1 or not isinstance(messages[0], Message): raise ValueError("Invalid args, a tuple of List[Message] is expected") statement_patterns = self.TOPIC_STATEMENTS.get(self.topic, []) @@ -49,7 +48,7 @@ class WriteTeachingPlanPart(Action): prompt = formatter.format(formation=self.FORMATION, role=self.prefix, statements="\n".join(statements), - lesson=args[0][0].content, + lesson=messages[0].content, topic=self.topic, language=self.language) diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index fede9f74a..5d10c4d17 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -10,6 +10,7 @@ from pathlib import Path import aiofiles from metagpt.actions.write_teaching_plan import WriteTeachingPlanPart, TeachingPlanRequirement +from metagpt.const import WORKSPACE_ROOT from metagpt.roles import Role from metagpt.schema import Message from metagpt.logs import logger @@ -59,7 +60,7 @@ class Teacher(Role): async def save(self, content): """Save teaching plan""" filename = Teacher.new_file_name(self.course_title) - pathname = Path(__file__).resolve().parent.parent.parent / "output" + pathname = WORKSPACE_ROOT / "output" pathname.mkdir(exist_ok=True) pathname = pathname / filename try: diff --git a/tests/metagpt/actions/test_ui_design.py b/tests/metagpt/actions/test_ui_design.py index d284b20f2..dedd0b30e 100644 --- a/tests/metagpt/actions/test_ui_design.py +++ b/tests/metagpt/actions/test_ui_design.py @@ -4,7 +4,7 @@ # from tests.metagpt.roles.ui_role import UIDesign -llm_resp= ''' +llm_resp = ''' # UI Design Description ```The user interface for the snake game will be designed in a way that is simple, clean, and intuitive. The main elements of the game such as the game grid, snake, food, score, and game over message will be clearly defined and easy to understand. The game grid will be centered on the screen with the score displayed at the top. The game controls will be intuitive and easy to use. The design will be modern and minimalist with a pleasing color scheme.``` @@ -100,6 +100,7 @@ body { font-size: 3em; ''' + def test_ui_design_parse_css(): ui_design_work = UIDesign(name="UI design action") @@ -161,7 +162,7 @@ def test_ui_design_parse_css(): transform: translate(-50%, -50%); font-size: 3em; ''' - assert ui_design_work.parse_css_code(context=llm_resp)==css + assert ui_design_work.parse_css_code(context=llm_resp) == css def test_ui_design_parse_html(): @@ -185,7 +186,4 @@ def test_ui_design_parse_html(): ''' - assert ui_design_work.parse_css_code(context=llm_resp)==html - - - + assert ui_design_work.parse_css_code(context=llm_resp) == html diff --git a/tests/metagpt/actions/test_write_code.py b/tests/metagpt/actions/test_write_code.py index 7bb18ddf2..2d4c496e1 100644 --- a/tests/metagpt/actions/test_write_code.py +++ b/tests/metagpt/actions/test_write_code.py @@ -4,6 +4,7 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : test_write_code.py +@Modified By: mashenquan, 2023-8-1, fix-bug: `filename` of `write_code.run()` is missing. """ import pytest @@ -18,7 +19,7 @@ async def test_write_code(): api_design = "设计一个名为'add'的函数,该函数接受两个整数作为输入,并返回它们的和。" write_code = WriteCode("write_code") - code = await write_code.run(api_design) + code = await write_code.run(context=api_design, filename="test") logger.info(code) # 我们不能精确地预测生成的代码,但我们可以检查某些关键字 From a56e9a29e3243d6584c326a171e19dc38bf2f906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 10:53:34 +0800 Subject: [PATCH 18/41] feat: change save to --- metagpt/roles/teacher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index 5d10c4d17..95d54133b 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -60,7 +60,7 @@ class Teacher(Role): async def save(self, content): """Save teaching plan""" filename = Teacher.new_file_name(self.course_title) - pathname = WORKSPACE_ROOT / "output" + pathname = WORKSPACE_ROOT / "teaching_plan" pathname.mkdir(exist_ok=True) pathname = pathname / filename try: From 8f5b3e076e9655f0dde939dd9ed6e3a9e49ac27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 11:52:50 +0800 Subject: [PATCH 19/41] feat: +Choice Questions, Translation Questions --- metagpt/actions/write_teaching_plan.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index e8fe110d8..2916d7309 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -90,7 +90,7 @@ class WriteTeachingPlanPart(Action): COURSE_TITLE, "Teaching Hours", "Teaching Objectives", "Teaching Content", "Teaching Methods and Strategies", "Learning Activities", "Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement", - "Vocabulary Cloze", "Grammar Questions" + "Vocabulary Cloze", "Choice Questions", "Grammar Questions", "Translation Questions" ] TOPIC_STATEMENTS = { @@ -109,13 +109,20 @@ class WriteTeachingPlanPart(Action): ], "Vocabulary Cloze": [ "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create vocabulary cloze. The cloze should be in either {language} with " - "{teaching_language} answers or {teaching_language} with {language} answers. The key-related vocabulary " - "and phrases in the textbook content must all be included in the exercises." + "create vocabulary cloze. The cloze should include 10 {language} questions with {teaching_language} " + "answers, and it should also include 10 {teaching_language} questions with {language} answers. " + "The key-related vocabulary and phrases in the textbook content must all be included in the exercises.", ], "Grammar Questions": [ "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create grammar questions. "] + "create grammar questions. 10 questions."], + "Choice Questions": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create choice questions. 10 questions."], + "Translation Questions": [ + "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " + "create translation questions. 10 questions." + ] } # Teaching plan title From 0c1febfc77555df93051f00e245d38d626028b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 12:06:03 +0800 Subject: [PATCH 20/41] feat: +Choice Questions, Translation Questions --- metagpt/actions/write_teaching_plan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 2916d7309..1c9b1a86e 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -121,7 +121,9 @@ class WriteTeachingPlanPart(Action): "create choice questions. 10 questions."], "Translation Questions": [ "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", " - "create translation questions. 10 questions." + "create translation questions. The translation should include 10 {language} questions with " + "{teaching_language} answers, and it should also include 10 {teaching_language} questions with " + "{language} answers." ] } From e5885ec99ae2ab3fdb0d459d8572bfe03b646e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 12:07:58 +0800 Subject: [PATCH 21/41] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/roles/role.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 47aa90197..36dfb2d36 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -282,8 +282,7 @@ class Role: @staticmethod def format_value(value, options): - """Fill parameters inside `value` with `options`. - """ + """Fill parameters inside `value` with `options`.""" if "{" not in value: return value From a4017a1eeca186ac41cec05a851f200fab8c33d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 13:08:17 +0800 Subject: [PATCH 22/41] feat: + annotation --- examples/write_teaching_plan.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/write_teaching_plan.py b/examples/write_teaching_plan.py index ec8ad8948..da97a5463 100644 --- a/examples/write_teaching_plan.py +++ b/examples/write_teaching_plan.py @@ -80,9 +80,9 @@ async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, * def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): """ We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. - :param idea: Your innovative idea for `software` industry, such as "Creating a snake game."; lesson filename for `education` industry. + :param idea: lesson filename. :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. - :param n_round: + :param n_round: Reserved. :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` :return: @@ -91,4 +91,11 @@ def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): if __name__ == '__main__': + """ + Formats: + ``` + python write_teaching_plan.py lesson_filename --teaching_language= --language= + ``` + If `lesson_filename` is not available, a demo lesson content will be used. + """ fire.Fire(main) From b8901f2bb17cde7eb4cff9e85e1269d0664ec1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 1 Aug 2023 13:23:17 +0800 Subject: [PATCH 23/41] feat: +annotation --- metagpt/actions/write_teaching_plan.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 1c9b1a86e..3718c9801 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -23,11 +23,11 @@ class WriteTeachingPlanPart(Action): def __init__(self, name: str = "", context=None, llm=None, topic: str = "", language: str = "Chinese"): """ - Args: - name: action name - context: context - llm: object of :class:`LLM` - topic: topic part of teaching plan + :param name: action name + :param context: context + :param llm: object of :class:`LLM` + :param topic: topic part of teaching plan + :param language: A human language, such as Chinese, English, French, etc. """ super().__init__(name, context, llm) self.topic = topic From 80a189ad4a1546f8c1a9dbe00c42725868c35e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 7 Aug 2023 11:24:34 +0800 Subject: [PATCH 24/41] feat: +meta role --- .gitignore | 1 + config/pattern/write_teaching_plan.yaml | 51 +++++++++++++ examples/write_teaching_plan.py | 5 +- metagpt/roles/fork_meta_role.py | 98 +++++++++++++++++++++++++ metagpt/roles/meta_role.py | 29 ++++++++ 5 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 config/pattern/write_teaching_plan.yaml create mode 100644 metagpt/roles/fork_meta_role.py create mode 100644 metagpt/roles/meta_role.py diff --git a/.gitignore b/.gitignore index 3ec71f8b6..e326e8372 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,7 @@ workspace/* *.mmd tmp output.wav +*.bak # output folder output diff --git a/config/pattern/write_teaching_plan.yaml b/config/pattern/write_teaching_plan.yaml new file mode 100644 index 000000000..fbd8b4ae9 --- /dev/null +++ b/config/pattern/write_teaching_plan.yaml @@ -0,0 +1,51 @@ +# `fork` role demo +- role_type: "fork" + name: "Lily" + profile: "{teaching_language} Teacher" + goal: "writing a {language} teaching plan part by part" + constraints: "writing in {language}" + desc: "" + actions: + - name: "" + topic: "Title" + language: "Chinese" + statements: + - "Statement: Find and return the title of the lesson only in markdown first-level header format, without anything else." + - name: "" + topic: "Teaching Content" + language: "Chinese" + statements: + - "Statement: \"Teaching Content\" must include vocabulary, analysis, and examples of various grammar structures that appear in the textbook, as well as the listening materials and key points." + - "Statement: \"Teaching Content\" must include more examples." + - name: "" + topic: "Teaching Time Allocation" + language: "Chinese" + statements: + - "Statement: \"Teaching Time Allocation\" must include how much time is allocated to each part of the textbook content." + - name: "" + topic: "Teaching Methods and Strategies" + language: "Chinese" + statements: + - "Statement: \"Teaching Methods and Strategies\" must include teaching focus, difficulties, materials, procedures, in detail." + - name: "" + topic: "Vocabulary Cloze" + language: "Chinese" + statements: + - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create vocabulary cloze. The cloze should include 10 {language} questions with {teaching_language} answers, and it should also include 10 {teaching_language} questions with {language} answers. The key-related vocabulary and phrases in the textbook content must all be included in the exercises." + - name: "" + topic: "Grammar Questions" + language: "Chinese" + statements: + - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create grammar questions. 10 questions." + - name: "" + topic: "Choice Questions" + language: "Chinese" + statements: + - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create choice questions. 10 questions." + - name: "" + topic: "Translation Questions" + language: "Chinese" + statements: + - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create translation questions. The translation should include 10 {language} questions with {teaching_language} answers, and it should also include 10 {teaching_language} questions with {language} answers." + + diff --git a/examples/write_teaching_plan.py b/examples/write_teaching_plan.py index da97a5463..30a8d8366 100644 --- a/examples/write_teaching_plan.py +++ b/examples/write_teaching_plan.py @@ -1,7 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Modified By: mashenquan, 2023-07-27, + `industry` concept +@Time : 2023-07-27 +@Author : mashenquan +@File : write_teaching_plan.py +@Desc: Write teaching plan demo """ import asyncio diff --git a/metagpt/roles/fork_meta_role.py b/metagpt/roles/fork_meta_role.py new file mode 100644 index 000000000..1a69b9ca7 --- /dev/null +++ b/metagpt/roles/fork_meta_role.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/7 +@Author : mashenquan +@File : fork_meta_role.py +@Desc : 我试图将UML的一些符号概念引入到MetaGPT,使其具备通过符号拼接自由搭建flow的能力。同时我也尝试将这些符号做得配置化和标准化,让flow搭建流程更便捷。这是一个`fork` meta-role demo,实现的是write_teaching_plan功能。 +""" + + + +async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, *args, **kwargs): + """Run a startup. Be a teacher in education industry.""" + + demo_lesson = """ + UNIT 1 Making New Friends + TOPIC 1 Welcome to China! + Section A + + 1a Listen and number the following names. + Jane Mari Kangkang Michael + Look, listen and understand. Then practice the conversation. + Work in groups. Introduce yourself using + I ’m ... Then practice 1a + with your own hometown or the following places. + + 1b Listen and number the following names + Jane Michael Maria Kangkang + 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places. + China the USA the UK Hong Kong Beijing + + 2a Look, listen and understand. Then practice the conversation + Hello! + Hello! + Hello! + Hello! Are you Maria? + No, I’m not. I’m Jane. + Oh, nice to meet you, Jane + Nice to meet you, too. + Hi, Maria! + Hi, Kangkang! + Welcome to China! + Thanks. + + 2b Work in groups. Make up a conversation with your own name and the + following structures. + A: Hello! / Good morning! / Hi! I’m ... Are you ... ? + B: ... + + 3a Listen, say and trace + Aa Bb Cc Dd Ee Ff Gg + + 3b Listen and number the following letters. Then circle the letters with the same sound as Bb. + Aa Bb Cc Dd Ee Ff Gg + + 3c Match the big letters with the small ones. Then write them on the lines. + """ + + lesson = "" + if lesson_file is not None and Path(lesson_file).exists(): + async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: + lesson = await reader.read() + logger.info(f"Course content: {lesson}") + if not lesson: + logger.info("No course content provided, using the demo course.") + lesson = demo_lesson + + + + company = SoftwareCompany() + company.hire([(*args, **kwargs)]) + company.invest(investment) + company.start_project(lesson, role="Teacher", cause_by=TeachingPlanRequirement) + await company.run(n_round=1) + + +def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): + """ + We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. + :param idea: lesson filename. + :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. + :param n_round: Reserved. + :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` + :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` + :return: + """ + asyncio.run(startup(idea, investment, n_round, *args, **kwargs)) + + +if __name__ == '__main__': + """ + Formats: + ``` + python write_teaching_plan.py lesson_filename --teaching_language= --language= + ``` + If `lesson_filename` is not available, a demo lesson content will be used. + """ + fire.Fire(main) diff --git a/metagpt/roles/meta_role.py b/metagpt/roles/meta_role.py new file mode 100644 index 000000000..1da180355 --- /dev/null +++ b/metagpt/roles/meta_role.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/7 +@Author : mashenquan +@File : meta_role.py +@Desc : 我试图将UML的一些符号概念引入到MetaGPT,使其具备通过符号拼接自由搭建flow的能力。同时我也尝试将这些符号做得配置化和标准化,让flow搭建流程更便捷。 + 分工参照UML 2.0 activity diagrams: `https://www.uml-diagrams.org/activity-diagrams.html` +""" +from typing import Dict, List + +from metagpt.roles import Role +from pydantic import BaseModel + +class UMLMetaRoleArgs(BaseModel): + role_type: str + name: str = "" + profile: str = "" + goal: str = "" + constraints: str = "" + desc: str = "" + actions: List + +class UMLMetaRole(Role): + """UML activity roles抽象父类""" + + def __init__(self, role_args: Dict): + """""" + self.role_args From 5702aaa5ad4e9113e954ab16e1fde1965b3f591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 7 Aug 2023 21:12:27 +0800 Subject: [PATCH 25/41] feat: + uml fork style role demo --- config/pattern/template.yaml | 40 +++++ config/pattern/write_teaching_plan.yaml | 103 +++++++++++-- examples/fork_meta_role.py | 121 +++++++++++++++ metagpt/actions/meta_action.py | 61 ++++++++ metagpt/memory/memory.py | 10 +- metagpt/roles/fork_meta_role.py | 187 ++++++++++++++---------- metagpt/roles/meta_role.py | 29 ---- metagpt/roles/role.py | 6 +- metagpt/roles/teacher.py | 2 +- metagpt/roles/uml_meta_role_factory.py | 43 ++++++ metagpt/roles/uml_meta_role_options.py | 69 +++++++++ 11 files changed, 543 insertions(+), 128 deletions(-) create mode 100644 config/pattern/template.yaml create mode 100644 examples/fork_meta_role.py create mode 100644 metagpt/actions/meta_action.py delete mode 100644 metagpt/roles/meta_role.py create mode 100644 metagpt/roles/uml_meta_role_factory.py create mode 100644 metagpt/roles/uml_meta_role_options.py diff --git a/config/pattern/template.yaml b/config/pattern/template.yaml new file mode 100644 index 000000000..d148804f0 --- /dev/null +++ b/config/pattern/template.yaml @@ -0,0 +1,40 @@ +# Pattern Configuration Template +# Created By: mashenquan, 2023-8-7 +# File Name: template.yaml +# This template defines a set of structural standards for generating roles and action flows based on configurations. +# For more about UML 2.0 activity diagrams, see: `https://www.uml-diagrams.org/activity-diagrams.html` + +# project settings +startup: + requirement: "TeachingPlanRequirement" # Defines project initial requirement action + role: "Teacher" # Defines project role + investment: 3.0 # Defines the max project investment + n_round: 1 # Defines the max project round count + +# roles settings +roles: # A project can involve multiple roles. +- role_type: "fork" # `fork` type role corresponds to the functional positioning of the `fork` node in UML 2.0 activity diagrams. + name: "Lily" + profile: "{teaching_language} Teacher" + goal: "writing a {language} teaching plan part by part" + constraints: "writing in {language}" + role: "You are a {teaching_language} Teacher, named Lily, your goal is ..." + desc: "" + output_filename: "teaching_plan_demo.md" + requirement: ["TeachingPlanRequirement"] + templates: # The template provides a convenient way to generate prompts. After each action selects its respective template, you only need to provide the corresponding variable values. Variable replacement is automatically handled by the framework. + - "Do ..." + - "Do ..." + # role's action settings + actions: # A role can have multiple actions. + - name: "" + topic: "Title" + language: "Chinese" + statements: # When replacing template variables, multiple statements will be joined into a single string using line breaks. + - "Statement: Find and return ..." + template_ix: 0 + rsp_begin_tag: "[..._BEGIN]" # When asking, request the LLM to include the tag in the response. It's optional. + rsp_end_tag: "[..._END]" # When asking, request the LLM to include the tag in the response. It's optional. + + + diff --git a/config/pattern/write_teaching_plan.yaml b/config/pattern/write_teaching_plan.yaml index fbd8b4ae9..6a05bad96 100644 --- a/config/pattern/write_teaching_plan.yaml +++ b/config/pattern/write_teaching_plan.yaml @@ -1,51 +1,124 @@ -# `fork` role demo -- role_type: "fork" +# The `fork` role demo implements the flow of the code in `examples/write_teaching_plan.py`. + +# project settings +startup: + requirement: "TeachingPlanRequirement" # Defines project initial requirement action + role: "Teacher" + investment: 3.0 + n_round: 1 + +# roles settings +roles: # A project can involve multiple roles. +- role_type: "fork" # `fork` type role corresponds to the functional positioning of the `fork` node in UML 2.0 activity diagrams. name: "Lily" profile: "{teaching_language} Teacher" goal: "writing a {language} teaching plan part by part" constraints: "writing in {language}" + role: "You are a {teaching_language} Teacher, named Lily, your goal is writing a {teaching_language} teaching plan part by part, and the constraint is writing in {language}." desc: "" - actions: + output_filename: "teaching_plan_demo.md" + requirement: ["TeachingPlanRequirement"] + templates: # The template provides a convenient way to generate prompts. After each action selects its respective template, you only need to provide the corresponding variable values. Variable replacement is automatically handled by the framework. + - "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\n{statements}\nConstraint: Writing in {language}.\nAnswer options: Encloses the lesson title with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\n[LESSON_BEGIN]\n{lesson}\n[LESSON_END]" + - "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: {role}\nStatement: Write the \"{topic}\" part of teaching plan, WITHOUT ANY content unrelated to \"{topic}\"!!\n{statements}\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in {language}.\n[LESSON_BEGIN]\n{lesson}\n[LESSON_END]" + actions: # 一个role可以有多个action - name: "" topic: "Title" language: "Chinese" - statements: - - "Statement: Find and return the title of the lesson only in markdown first-level header format, without anything else." + statements: # When replacing template variables, multiple statements will be joined into a single string using line breaks. + - "Statement: Find and return the title of the lesson only with \"# \" prefixed, without anything else." + template_ix: 0 + - name: "" + topic: "Teaching Hours" + language: "Chinese" + statements: [] + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" # When asking, request the LLM to include the tag in the response. It's optional. + rsp_end_tag: "[TEACHING_PLAN_END]" # When asking, request the LLM to include the tag in the response. It's optional. + - name: "" + topic: "Teaching Objectives" + language: "Chinese" + statements: [] + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" - name: "" topic: "Teaching Content" language: "Chinese" statements: - "Statement: \"Teaching Content\" must include vocabulary, analysis, and examples of various grammar structures that appear in the textbook, as well as the listening materials and key points." - "Statement: \"Teaching Content\" must include more examples." - - name: "" - topic: "Teaching Time Allocation" - language: "Chinese" - statements: - - "Statement: \"Teaching Time Allocation\" must include how much time is allocated to each part of the textbook content." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" - name: "" topic: "Teaching Methods and Strategies" language: "Chinese" statements: - "Statement: \"Teaching Methods and Strategies\" must include teaching focus, difficulties, materials, procedures, in detail." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" + - name: "" + topic: "Learning Activities" + language: "Chinese" + statements: [] + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" + - name: "" + topic: "Teaching Time Allocation" + language: "Chinese" + statements: + - "Statement: \"Teaching Time Allocation\" must include how much time is allocated to each part of the textbook content." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" + - name: "" + topic: "Assessment and Feedback" + language: "Chinese" + statements: [] + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" + - name: "" + topic: "Teaching Summary and Improvement" + language: "Chinese" + statements: [] + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" - name: "" topic: "Vocabulary Cloze" language: "Chinese" statements: - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create vocabulary cloze. The cloze should include 10 {language} questions with {teaching_language} answers, and it should also include 10 {teaching_language} questions with {language} answers. The key-related vocabulary and phrases in the textbook content must all be included in the exercises." - - name: "" - topic: "Grammar Questions" - language: "Chinese" - statements: - - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create grammar questions. 10 questions." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" - name: "" topic: "Choice Questions" language: "Chinese" statements: - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create choice questions. 10 questions." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" + - name: "" + topic: "Grammar Questions" + language: "Chinese" + statements: + - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create grammar questions. 10 questions." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" - name: "" topic: "Translation Questions" language: "Chinese" statements: - "Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create translation questions. The translation should include 10 {language} questions with {teaching_language} answers, and it should also include 10 {teaching_language} questions with {language} answers." + template_ix: 1 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" diff --git a/examples/fork_meta_role.py b/examples/fork_meta_role.py new file mode 100644 index 000000000..21e3b5f7c --- /dev/null +++ b/examples/fork_meta_role.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/7 +@Author : mashenquan +@File : fork_meta_role.py +@Desc : I am attempting to incorporate certain symbol concepts from UML into MetaGPT, enabling it to possess the + ability to construct flows freely by concatenating symbols. Simultaneously, I am also striving to make + these symbols configurable and standardized, making the process of building flow structures more + convenient. This is a fork meta-role demo that implements the functionality of + `examples/write_teaching_plan.py`. +""" + +import asyncio +from pathlib import Path + +import aiofiles +import fire +import yaml + +from metagpt.actions.meta_action import MetaAction +from metagpt.logs import logger +from metagpt.roles.uml_meta_role_factory import UMLMetaRoleFactory +from metagpt.roles.uml_meta_role_options import ProjectConfig +from metagpt.software_company import SoftwareCompany + + +async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, *args, **kwargs): + """Run a startup. Be a teacher in education industry.""" + + demo_lesson = """ + UNIT 1 Making New Friends + TOPIC 1 Welcome to China! + Section A + + 1a Listen and number the following names. + Jane Mari Kangkang Michael + Look, listen and understand. Then practice the conversation. + Work in groups. Introduce yourself using + I ’m ... Then practice 1a + with your own hometown or the following places. + + 1b Listen and number the following names + Jane Michael Maria Kangkang + 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places. + China the USA the UK Hong Kong Beijing + + 2a Look, listen and understand. Then practice the conversation + Hello! + Hello! + Hello! + Hello! Are you Maria? + No, I’m not. I’m Jane. + Oh, nice to meet you, Jane + Nice to meet you, too. + Hi, Maria! + Hi, Kangkang! + Welcome to China! + Thanks. + + 2b Work in groups. Make up a conversation with your own name and the + following structures. + A: Hello! / Good morning! / Hi! I’m ... Are you ... ? + B: ... + + 3a Listen, say and trace + Aa Bb Cc Dd Ee Ff Gg + + 3b Listen and number the following letters. Then circle the letters with the same sound as Bb. + Aa Bb Cc Dd Ee Ff Gg + + 3c Match the big letters with the small ones. Then write them on the lines. + """ + + lesson = "" + if lesson_file is not None and Path(lesson_file).exists(): + async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: + lesson = await reader.read() + logger.info(f"Course content: {lesson}") + if not lesson: + logger.info("No course content provided, using the demo course.") + lesson = demo_lesson + + yaml_filename = kwargs["config"] + kwargs["lesson"] = lesson + + with open(yaml_filename, "r") as reader: + configs = yaml.safe_load(reader) + + startup_config = ProjectConfig(**configs) + roles = UMLMetaRoleFactory.create_roles(startup_config.roles, **kwargs) + company = SoftwareCompany() + company.hire(roles) + company.invest(startup_config.startup.investment) + company.start_project(lesson, role=startup_config.startup.role, + cause_by=MetaAction.get_action_type(startup_config.startup.requirement)) + await company.run(n_round=startup_config.startup.n_round) + + +def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): + """ + We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. + :param idea: lesson filename. + :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. + :param n_round: Reserved. + :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` + :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` + :return: + """ + asyncio.run(startup(idea, investment, n_round, *args, **kwargs)) + + +if __name__ == '__main__': + """ + Formats: + ``` + python write_teaching_plan.py lesson_filename --teaching_language= --language= + ``` + If `lesson_filename` is not available, a demo lesson content will be used. + """ + fire.Fire(main) diff --git a/metagpt/actions/meta_action.py b/metagpt/actions/meta_action.py new file mode 100644 index 000000000..3f01b8c0f --- /dev/null +++ b/metagpt/actions/meta_action.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/7 +@Author : mashenquan +@File : meta_action.py +@Desc : I am attempting to incorporate certain symbol concepts from UML into MetaGPT, enabling it to have the + ability to freely construct flows through symbol concatenation. Simultaneously, I am also striving to + make these symbols configurable and standardized, making the process of building flows more convenient. + For more about `fork` node in activity diagrams, see: `https://www.uml-diagrams.org/activity-diagrams.html` + This file defines a meta action capable of generating arbitrary actions at runtime based on a + configuration file. +""" + +from typing import Type + +from metagpt.actions import Action +from metagpt.logs import logger +from metagpt.roles.uml_meta_role_options import MetaActionOptions +from metagpt.schema import Message + + +class MetaAction(Action): + def __init__(self, options: MetaActionOptions, llm=None, **kwargs): + super(MetaAction, self).__init__(options.name, kwargs.get("context"), llm=llm) + self.prompt = options.format_prompt(**kwargs) + self.options = options + self.kwargs = kwargs + + def __str__(self): + """Return `topic` value when str()""" + return self.options.topic + + def __repr__(self): + """Show `topic` value when debug""" + return self.options.topic + + async def run(self, messages, *args, **kwargs): + if len(messages) < 1 or not isinstance(messages[0], Message): + raise ValueError("Invalid args, a tuple of List[Message] is expected") + + logger.debug(self.prompt) + rsp = await self._aask(prompt=self.prompt) + logger.debug(rsp) + self._set_result(rsp) + return self.rsp + + def _set_result(self, rsp): + if self.options.rsp_begin_tag and self.options.rsp_begin_tag in rsp: + ix = rsp.index(self.options.rsp_begin_tag) + rsp = rsp[ix + len(self.options.rsp_begin_tag):] + if self.options.rsp_end_tag and self.options.rsp_end_tag in rsp: + ix = rsp.index(self.options.rsp_end_tag) + rsp = rsp[0:ix] + self.rsp = rsp.strip() + + @staticmethod + def get_action_type(topic: str): + """Create a runtime :class:`Action` subclass""" + action_type: Type["Action"] = type(topic, (Action,), {"name": topic}) + return action_type diff --git a/metagpt/memory/memory.py b/metagpt/memory/memory.py index a96aaf1be..625d98675 100644 --- a/metagpt/memory/memory.py +++ b/metagpt/memory/memory.py @@ -4,6 +4,8 @@ @Time : 2023/5/20 12:15 @Author : alexanderwu @File : memory.py +@Modified By: mashenquan, 2023-8-7. Modified get_by_actions() to support for dynamically generated Action classes + at runtime. """ from collections import defaultdict from typing import Iterable, Type @@ -80,8 +82,12 @@ class Memory: def get_by_actions(self, actions: Iterable[Type[Action]]) -> list[Message]: """Return all messages triggered by specified Actions""" rsp = [] + # Using the `type(obj).__name__` approach to support the runtime creation of requirement classes. + # See `MetaAction.get_action_type()` for more. + class_names = {type(k).__name__: k for k in self.index.keys()} for action in actions: - if action not in self.index: + if type(action).__name__ not in class_names: continue - rsp += self.index[action] + key = class_names[type(action).__name__] + rsp += self.index[key] return rsp diff --git a/metagpt/roles/fork_meta_role.py b/metagpt/roles/fork_meta_role.py index 1a69b9ca7..555bc8cf3 100644 --- a/metagpt/roles/fork_meta_role.py +++ b/metagpt/roles/fork_meta_role.py @@ -4,95 +4,124 @@ @Time : 2023/8/7 @Author : mashenquan @File : fork_meta_role.py -@Desc : 我试图将UML的一些符号概念引入到MetaGPT,使其具备通过符号拼接自由搭建flow的能力。同时我也尝试将这些符号做得配置化和标准化,让flow搭建流程更便捷。这是一个`fork` meta-role demo,实现的是write_teaching_plan功能。 +@Desc : I am attempting to incorporate certain symbol concepts from UML into MetaGPT, enabling it to have the + ability to freely construct flows through symbol concatenation. Simultaneously, I am also striving to + make these symbols configurable and standardized, making the process of building flows more convenient. + For more about `fork` node in activity diagrams, see: `https://www.uml-diagrams.org/activity-diagrams.html` + This file defines a `fork` style meta role capable of generating arbitrary roles at runtime based on a + configuration file. """ +import re + +import aiofiles + +from metagpt.actions.meta_action import MetaAction +from metagpt.const import WORKSPACE_ROOT +from metagpt.logs import logger +from metagpt.roles import Role +from metagpt.roles.uml_meta_role_options import MetaActionOptions, UMLMetaRoleOptions +from metagpt.schema import Message -async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, *args, **kwargs): - """Run a startup. Be a teacher in education industry.""" +class ForkMetaRole(Role): + """A `fork` style meta role capable of generating arbitrary roles at runtime based on a configuration file""" + def __init__(self, options, **kwargs): + """Initialize a `fork` style meta role - demo_lesson = """ - UNIT 1 Making New Friends - TOPIC 1 Welcome to China! - Section A + :param options: pattern yaml file data + :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` + :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` + """ + opts = UMLMetaRoleOptions(**options) + global_variables = { + "name": Role.format_value(opts.name, kwargs), + "profile": Role.format_value(opts.profile, kwargs), + "goal": Role.format_value(opts.goal, kwargs), + "constraints": Role.format_value(opts.constraints, kwargs), + "desc": Role.format_value(opts.desc, kwargs), + "role": Role.format_value(opts.role, kwargs) + } + for k, v in kwargs.items(): + if k not in global_variables: + global_variables[k] = v - 1a Listen and number the following names. - Jane Mari Kangkang Michael - Look, listen and understand. Then practice the conversation. - Work in groups. Introduce yourself using - I ’m ... Then practice 1a - with your own hometown or the following places. + super(ForkMetaRole, self).__init__( + name=global_variables["name"], + profile=global_variables["profile"], + goal=global_variables["goal"], + constraints=global_variables["constraints"], + desc=global_variables["desc"], + **kwargs + ) + self.options = options + actions = [] + for m in opts.actions: + for k, v in m.items(): + v = Role.format_value(v, kwargs) + m[k] = v + for k, v in global_variables.items(): + if k not in m: + m[k] = v - 1b Listen and number the following names - Jane Michael Maria Kangkang - 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places. - China the USA the UK Hong Kong Beijing + o = MetaActionOptions(**m) + o.set_default_template(opts.templates[o.template_ix]) - 2a Look, listen and understand. Then practice the conversation - Hello! - Hello! - Hello! - Hello! Are you Maria? - No, I’m not. I’m Jane. - Oh, nice to meet you, Jane - Nice to meet you, too. - Hi, Maria! - Hi, Kangkang! - Welcome to China! - Thanks. + act = MetaAction(options=o, llm=self._llm, **m) + actions.append(act) + self._init_actions(actions) + requirement_types = set() + for v in opts.requirement: + requirement_types.add(MetaAction.get_action_type(v)) + self._watch(requirement_types) - 2b Work in groups. Make up a conversation with your own name and the - following structures. - A: Hello! / Good morning! / Hi! I’m ... Are you ... ? - B: ... + async def _think(self) -> None: + """Everything will be done part by part.""" + if self._rc.todo is None: + self._set_state(0) + return - 3a Listen, say and trace - Aa Bb Cc Dd Ee Ff Gg + if self._rc.state + 1 < len(self._states): + self._set_state(self._rc.state + 1) + else: + self._rc.todo = None - 3b Listen and number the following letters. Then circle the letters with the same sound as Bb. - Aa Bb Cc Dd Ee Ff Gg + async def _react(self) -> Message: + ret = Message(content="") + while True: + await self._think() + if self._rc.todo is None: + break + logger.debug(f"{self._setting}: {self._rc.state=}, will do {self._rc.todo}") + msg = await self._act() + if ret.content != '': + ret.content += "\n\n\n" + ret.content += msg.content + logger.info(ret.content) + await self.save(ret.content) + return ret - 3c Match the big letters with the small ones. Then write them on the lines. - """ + async def save(self, content): + """Save teaching plan""" + output_filename = self.options.get("output_filename") + if not output_filename: + return + filename = ForkMetaRole.new_file_name(output_filename) + pathname = WORKSPACE_ROOT / "teaching_plan" + pathname.mkdir(exist_ok=True) + pathname = pathname / filename + try: + async with aiofiles.open(str(pathname), mode='w', encoding='utf-8') as writer: + await writer.write(content) + except Exception as e: + logger.error(f'Save failed:{e}') + logger.info(f"Save to:{pathname}") - lesson = "" - if lesson_file is not None and Path(lesson_file).exists(): - async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: - lesson = await reader.read() - logger.info(f"Course content: {lesson}") - if not lesson: - logger.info("No course content provided, using the demo course.") - lesson = demo_lesson - - - - company = SoftwareCompany() - company.hire([(*args, **kwargs)]) - company.invest(investment) - company.start_project(lesson, role="Teacher", cause_by=TeachingPlanRequirement) - await company.run(n_round=1) - - -def main(idea: str, investment: float = 3.0, n_round: int = 5, *args, **kwargs): - """ - We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities. - :param idea: lesson filename. - :param investment: As an investor, you have the opportunity to contribute a certain dollar amount to this AI company. - :param n_round: Reserved. - :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` - :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` - :return: - """ - asyncio.run(startup(idea, investment, n_round, *args, **kwargs)) - - -if __name__ == '__main__': - """ - Formats: - ``` - python write_teaching_plan.py lesson_filename --teaching_language= --language= - ``` - If `lesson_filename` is not available, a demo lesson content will be used. - """ - fire.Fire(main) + @staticmethod + def new_file_name(lesson_title, ext=".md"): + """Create a related file name based on `lesson_title` and `ext`.""" + # Define the special characters that need to be replaced. + illegal_chars = r'[#@$%!*&\\/:*?"<>|\n\t \']' + # Replace the special characters with underscores. + filename = re.sub(illegal_chars, '_', lesson_title) + ext + return re.sub(r'_+', '_', filename) \ No newline at end of file diff --git a/metagpt/roles/meta_role.py b/metagpt/roles/meta_role.py deleted file mode 100644 index 1da180355..000000000 --- a/metagpt/roles/meta_role.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@Time : 2023/8/7 -@Author : mashenquan -@File : meta_role.py -@Desc : 我试图将UML的一些符号概念引入到MetaGPT,使其具备通过符号拼接自由搭建flow的能力。同时我也尝试将这些符号做得配置化和标准化,让flow搭建流程更便捷。 - 分工参照UML 2.0 activity diagrams: `https://www.uml-diagrams.org/activity-diagrams.html` -""" -from typing import Dict, List - -from metagpt.roles import Role -from pydantic import BaseModel - -class UMLMetaRoleArgs(BaseModel): - role_type: str - name: str = "" - profile: str = "" - goal: str = "" - constraints: str = "" - desc: str = "" - actions: List - -class UMLMetaRole(Role): - """UML activity roles抽象父类""" - - def __init__(self, role_args: Dict): - """""" - self.role_args diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index f79764324..1d65a7f26 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -4,7 +4,7 @@ @Time : 2023/5/11 14:42 @Author : alexanderwu @File : role.py -@Modified By: mashenquan, 2023-07-27, :class:`Role` + properties. +@Modified By: mashenquan, 2023-8-7, :class:`Role` + properties. """ from __future__ import annotations @@ -286,6 +286,8 @@ class Role: @staticmethod def format_value(value, options): """Fill parameters inside `value` with `options`.""" + if not isinstance(value, str): + return value if "{" not in value: return value @@ -295,7 +297,7 @@ class Role: except KeyError as e: logger.warning(f"Parameter is missing:{e}") for k, v in options.items(): - value = value.replace("{" + f"{k}" + "}", v) + value = value.replace("{" + f"{k}" + "}", str(v)) return value __DEFAULT_OPTIONS__ = { diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index 95d54133b..24ede7402 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -5,7 +5,7 @@ @Author : mashenquan @File : teacher.py """ -from pathlib import Path + import aiofiles diff --git a/metagpt/roles/uml_meta_role_factory.py b/metagpt/roles/uml_meta_role_factory.py new file mode 100644 index 000000000..78f9689a2 --- /dev/null +++ b/metagpt/roles/uml_meta_role_factory.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/7 +@Author : mashenquan +@File : uml_meta_role_factory.py +@Desc : I am attempting to incorporate certain symbol concepts from UML into MetaGPT, enabling it to have the + ability to freely construct flows through symbol concatenation. Simultaneously, I am also striving to + make these symbols configurable and standardized, making the process of building flows more convenient. + For more about `fork` node in activity diagrams, see: `https://www.uml-diagrams.org/activity-diagrams.html` +""" + +from metagpt.roles.fork_meta_role import ForkMetaRole +from metagpt.roles.uml_meta_role_options import UMLMetaRoleOptions + + +class UMLMetaRoleFactory: + """Factory of UML activity role classes""" + + @classmethod + def create_roles(cls, role_configs, **kwargs): + """Generate the flow of the project based on the configuration in the format of config/pattern/template.yaml. + + :param role_configs: `roles` field of template.yaml + :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` + + """ + roles = [] + for m in role_configs: + opt = UMLMetaRoleOptions(**m) + constructor = cls.CONSTRUCTORS.get(opt.role_type) + if constructor is None: + raise NotImplementedError( + f"{opt.role_type} is not implemented" + ) + r = constructor(m, **kwargs) + roles.append(r) + return roles + + CONSTRUCTORS = { + "fork": ForkMetaRole, + # TODO: add more activity node constructor here.. + } diff --git a/metagpt/roles/uml_meta_role_options.py b/metagpt/roles/uml_meta_role_options.py new file mode 100644 index 000000000..1d0fb322e --- /dev/null +++ b/metagpt/roles/uml_meta_role_options.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/7 +@Author : mashenquan +@File : uml_meta_role_options.py +@Desc : I am attempting to incorporate certain symbol concepts from UML into MetaGPT, enabling it to have the + ability to freely construct flows through symbol concatenation. Simultaneously, I am also striving to + make these symbols configurable and standardized, making the process of building flows more convenient. + For more about `fork` node in activity diagrams, see: `https://www.uml-diagrams.org/activity-diagrams.html` +""" + +from typing import List, Dict + +from pydantic import BaseModel + + +# `startup` field of config/pattern/template.yaml +class StartupConfig(BaseModel): + requirement: str + role: str + investment: float = 3.0 + n_round: int = 3 + + +# config/pattern/template.yaml +class ProjectConfig(BaseModel): + startup: StartupConfig + roles: List[Dict] + + +# element of `actions` field of config/pattern/template.yaml +class MetaActionOptions(BaseModel): + topic: str + name: str = "" + language: str = "Chinese" + template_ix: int = 0 + statements: List[str] = [] + template: str = "" + rsp_begin_tag: str = "" + rsp_end_tag: str = "" + + def set_default_template(self, v): + if not self.template: + self.template = v + + def format_prompt(self, **kwargs): + statements = "\n".join(self.statements) + opts = kwargs.copy() + opts["statements"] = statements + + from metagpt.roles import Role + prompt = Role.format_value(self.template, opts) + return prompt + + +# element of `roles` field of config/pattern/template.yaml +class UMLMetaRoleOptions(BaseModel): + role_type: str + name: str = "" + profile: str = "" + goal: str = "" + role: str = "" + constraints: str = "" + desc: str = "" + templates: List[str] = [] + output_filename: str = "" + actions: List + requirement: List From 76ea924a051232ed6e2d3e4dcb84906f35ac09bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 8 Aug 2023 15:25:26 +0800 Subject: [PATCH 26/41] feat: + unit test --- metagpt/roles/role.py | 5 ++ .../roles/test_uml_meta_role_factory.py | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/metagpt/roles/test_uml_meta_role_factory.py diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 1d65a7f26..68baeccf5 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -168,6 +168,11 @@ class Role: """Return role `constraints`, read only""" return self._setting.constraints + @property + def action_count(self): + """Return number of action""" + return len(self._actions) + def _get_prefix(self): """获取角色前缀""" if self._setting.desc: diff --git a/tests/metagpt/roles/test_uml_meta_role_factory.py b/tests/metagpt/roles/test_uml_meta_role_factory.py new file mode 100644 index 000000000..f59a30611 --- /dev/null +++ b/tests/metagpt/roles/test_uml_meta_role_factory.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/8 +@Author : mashenquan +@File : test_uml_meta_role_factory.py +""" +from typing import List, Dict + +from pydantic import BaseModel + +from metagpt.roles.uml_meta_role_factory import UMLMetaRoleFactory + + +def test_create_roles(): + class Inputs(BaseModel): + roles: List + kwargs: Dict + + inputs = [ + { + "roles": [ + { + "role_type": "fork", + "name": "Lily", + "profile": "{teaching_language} Teacher", + "goal": "writing a {language} teaching plan part by part", + "constraints": "writing in {language}", + "role": "You are a {teaching_language} Teacher, named Lily.", + "desc": "", + "output_filename": "teaching_plan_demo.md", + "requirement": ["TeachingPlanRequirement"], + "templates": ["Do 1 {statements}", "Do 2 {statements}"], + "actions": [ + { + "name": "", + "topic": "Title", + "language": "Chinese", + "statements": ["statement 1", "statement 2"]} + ], + "template_ix": 0 + } + ], + "kwargs": { + "teaching_language": "AA", + "language": "BB", + } + } + ] + + for i in inputs: + seed = Inputs(**i) + roles = UMLMetaRoleFactory.create_roles(seed.roles, **seed.kwargs) + assert len(roles) == 1 + assert "{" not in roles[0].profile + assert "{" not in roles[0].goal + assert roles[0].action_count == 1 + + +if __name__ == '__main__': + test_create_roles() From 1526680fc4b867668a84bf3813d02aefa0004044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 8 Aug 2023 18:02:49 +0800 Subject: [PATCH 27/41] feat: + unit test --- .../roles/test_uml_meta_role_options.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/metagpt/roles/test_uml_meta_role_options.py diff --git a/tests/metagpt/roles/test_uml_meta_role_options.py b/tests/metagpt/roles/test_uml_meta_role_options.py new file mode 100644 index 000000000..1eb66c50e --- /dev/null +++ b/tests/metagpt/roles/test_uml_meta_role_options.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/8 +@Author : mashenquan +@File : test_uml_meta_role_options.py +""" +from typing import List + +from pydantic import BaseModel + +from metagpt.roles.uml_meta_role_options import MetaActionOptions + + +def test_set_default_template(): + class Inputs(BaseModel): + statements: List + template: str + expect_prompt: str + + inputs = [ + { + "statements": ["Statement: 1", "Statement: 2"], + "template": "{statements}", + "expect_prompt": "Statement: 1\nStatement: 2" + } + ] + + for i in inputs: + seed = Inputs(**i) + opt = MetaActionOptions(topic="", statements=seed.statements) + assert opt.template == "" + opt.set_default_template(seed.template) + assert opt.template == seed.template + kwargs = {} + assert opt.format_prompt(**kwargs) == seed.expect_prompt + + +if __name__ == '__main__': + test_set_default_template() From 25f461b6a4ae2137c463202ce39769a107bbab89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 8 Aug 2023 19:02:57 +0800 Subject: [PATCH 28/41] feat: + unit test --- tests/metagpt/roles/test_fork_meta_role.py | 90 ++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/metagpt/roles/test_fork_meta_role.py diff --git a/tests/metagpt/roles/test_fork_meta_role.py b/tests/metagpt/roles/test_fork_meta_role.py new file mode 100644 index 000000000..b2659330d --- /dev/null +++ b/tests/metagpt/roles/test_fork_meta_role.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/8 +@Author : mashenquan +@File : test_fork_meta_role.py +""" +from typing import Dict + +from pydantic import BaseModel + +from metagpt.roles.fork_meta_role import ForkMetaRole + + +def test_creat_role(): + class Inputs(BaseModel): + role: Dict + action_count: int + + inputs = [ + { + "role": { + "role_type": "fork", + "name": "Lily", + "profile": "{teaching_language} Teacher", + "goal": "writing a {language} teaching plan part by part", + "constraints": "writing in {language}", + "role": "You are a {teaching_language} Teacher, named Lily, your goal is writing a {" + "teaching_language} teaching plan part by part, and the constraint is writing in {language}.", + "desc": "", + "output_filename": "teaching_plan_demo.md", + "requirement": ["TeachingPlanRequirement"], + "templates": [ + "Do not refer to the context of the previous conversation records, start the conversation " + "anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[" + "LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" " + "defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the " + "format requirements for your responses;\n\t\"Constraint\" defines the conditions that your " + "responses must comply with.\n\n{statements}\nConstraint: Writing in {language}.\nAnswer options: " + "Encloses the lesson title with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\n[" + "LESSON_BEGIN]\n{lesson}\n[LESSON_END]", + "Do not refer to the context of the previous conversation records, start the conversation " + "anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[" + "LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" " + "defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the " + "format requirements for your responses;\n\t\"Constraint\" defines the conditions that your " + "responses must comply with.\n\nCapacity and role: {role}\nStatement: Write the \"{topic}\" part " + "of teaching plan, WITHOUT ANY content unrelated to \"{topic}\"!!\n{statements}\nAnswer options: " + "Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" " + "tags.\nAnswer options: Using proper markdown format from second-level header " + "format.\nConstraint: Writing in {language}.\n[LESSON_BEGIN]\n{lesson}\n[LESSON_END] " + ], + "actions": [ + { + "name": "", + "topic": "Title", + "language": "Chinese", + "statements": [ + "Statement: Find and return the title of the lesson only with \"# \" prefixed, without " + "anything else."], + "template_ix": 0}, + { + "name": "", + "topic": "Teaching Hours", + "language": "Chinese", + "statements": [], + "template_ix": 1, + "rsp_begin_tag": "[TEACHING_PLAN_BEGIN]", + "rsp_end_tag": "[TEACHING_PLAN_END]"} + ] + }, + "action_count": 2 + } + ] + + for i in inputs: + seed = Inputs(**i) + kwargs = { + "teaching_language": "AA", + "language": "BB" + } + role = ForkMetaRole(seed.role, **kwargs) + assert role.action_count == 2 + assert "{" not in role.profile + assert "{" not in role.goal + assert "{" not in role.constraints + + +if __name__ == '__main__': + test_creat_role() From b0209f3d419e2feffa099902ba4f76429963e6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 8 Aug 2023 20:10:40 +0800 Subject: [PATCH 29/41] feat: + unit test --- tests/metagpt/actions/test_meta_action.py | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/metagpt/actions/test_meta_action.py diff --git a/tests/metagpt/actions/test_meta_action.py b/tests/metagpt/actions/test_meta_action.py new file mode 100644 index 000000000..cbaf3456c --- /dev/null +++ b/tests/metagpt/actions/test_meta_action.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/8 +@Author : mashenquan +@File : test_meta_action.py +""" +from typing import Dict + +from pydantic import BaseModel + +from metagpt.actions.meta_action import MetaAction +from metagpt.roles.uml_meta_role_options import MetaActionOptions + + +def test_meta_action_create(): + class Inputs(BaseModel): + options: Dict + kwargs: Dict + expect_class_name: str + expect_prompt: str + + inputs = [ + { + "options": { + "topic": "TOPIC_A", + "name": "A", + "language": "XX", + "template_ix": 0, + "statements": ["Statement A", "Statement B"], + "template": "{statements}", + "rsp_begin_tag": "", + "rsp_end_tag": "" + }, + "kwargs": {}, + "expect_class_name": "TOPIC_A", + "expect_prompt": "\n".join(["Statement A", "Statement B"]), + } + ] + + for i in inputs: + seed = Inputs(**i) + opt = MetaActionOptions(**seed.options) + act = MetaAction(opt, **seed.kwargs) + assert seed.expect_prompt == act.prompt + t = MetaAction.get_action_type(seed.expect_class_name) + assert t.__name__ == seed.expect_class_name + + +if __name__ == '__main__': + test_meta_action_create() From f8bc4e462575622927a2b7b022468bd95a38a8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 10:48:40 +0800 Subject: [PATCH 30/41] feat: test pass --- config/pattern/write_teaching_plan.yaml | 2 +- ...{fork_meta_role.py => fork_meta_role_write_teaching_plan.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/{fork_meta_role.py => fork_meta_role_write_teaching_plan.py} (100%) diff --git a/config/pattern/write_teaching_plan.yaml b/config/pattern/write_teaching_plan.yaml index 6a05bad96..357717908 100644 --- a/config/pattern/write_teaching_plan.yaml +++ b/config/pattern/write_teaching_plan.yaml @@ -16,7 +16,7 @@ roles: # A project can involve multiple r constraints: "writing in {language}" role: "You are a {teaching_language} Teacher, named Lily, your goal is writing a {teaching_language} teaching plan part by part, and the constraint is writing in {language}." desc: "" - output_filename: "teaching_plan_demo.md" + output_filename: "teaching_plan_demo" requirement: ["TeachingPlanRequirement"] templates: # The template provides a convenient way to generate prompts. After each action selects its respective template, you only need to provide the corresponding variable values. Variable replacement is automatically handled by the framework. - "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\n{statements}\nConstraint: Writing in {language}.\nAnswer options: Encloses the lesson title with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\n[LESSON_BEGIN]\n{lesson}\n[LESSON_END]" diff --git a/examples/fork_meta_role.py b/examples/fork_meta_role_write_teaching_plan.py similarity index 100% rename from examples/fork_meta_role.py rename to examples/fork_meta_role_write_teaching_plan.py From 9e91142c4ef30ef08ff9552a1b57bd954540e6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 13:46:22 +0800 Subject: [PATCH 31/41] fixbug: Align parameters with the parent class --- metagpt/actions/design_api.py | 2 +- metagpt/actions/project_management.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 1447eacc3..48fa7171a 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -135,7 +135,7 @@ class WriteDesign(Action): self._save_prd(docs_path, resources_path, context[-1].content) self._save_system_design(docs_path, resources_path, content) - async def run(self, context): + async def run(self, context, **kwargs): 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) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 89c59dcda..80b891bb8 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -115,7 +115,7 @@ class WriteTasks(Action): requirements_path = WORKSPACE_ROOT / ws_name / 'requirements.txt' requirements_path.write_text(rsp.instruct_content.dict().get("Required Python third-party packages").strip('"\n')) - async def run(self, context): + async def run(self, context, **kwargs): prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING) self._save(context, rsp) From 9e9e0d56e56ba2206f090bc040c14b580e1254d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 13:55:07 +0800 Subject: [PATCH 32/41] fixbug: Align parameters with the parent class --- metagpt/actions/design_api.py | 1 + metagpt/actions/project_management.py | 1 + 2 files changed, 2 insertions(+) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 48fa7171a..cf23e6ad1 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -4,6 +4,7 @@ @Time : 2023/5/11 19:26 @Author : alexanderwu @File : design_api.py +@Modified By: mashenquan, 2023-8-9, align `run` parameters with the parent :class:`Action` class. """ import shutil from pathlib import Path diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 80b891bb8..16473ff01 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -4,6 +4,7 @@ @Time : 2023/5/11 19:12 @Author : alexanderwu @File : project_management.py +@Modified By: mashenquan, 2023-8-9, align `run` parameters with the parent :class:`Action` class. """ from typing import List, Tuple From 35470dcee4d4635b798f03ccd6154467d65cb57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 14:40:43 +0800 Subject: [PATCH 33/41] fixbug: empty string causes aiofiles.open exepctition --- examples/write_teaching_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/write_teaching_plan.py b/examples/write_teaching_plan.py index da97a5463..86125f090 100644 --- a/examples/write_teaching_plan.py +++ b/examples/write_teaching_plan.py @@ -62,7 +62,7 @@ async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, * """ lesson = "" - if lesson_file is not None and Path(lesson_file).exists(): + if lesson_file and Path(lesson_file).exists(): async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: lesson = await reader.read() logger.info(f"Course content: {lesson}") From ed56aab79cc90ede26ca4993476e260721093fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 14:54:08 +0800 Subject: [PATCH 34/41] fixbug: empty string causes aiofiles.open exepctition --- config/pattern/write_teaching_plan.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/pattern/write_teaching_plan.yaml b/config/pattern/write_teaching_plan.yaml index 357717908..5b5f2af77 100644 --- a/config/pattern/write_teaching_plan.yaml +++ b/config/pattern/write_teaching_plan.yaml @@ -26,8 +26,10 @@ roles: # A project can involve multiple r topic: "Title" language: "Chinese" statements: # When replacing template variables, multiple statements will be joined into a single string using line breaks. - - "Statement: Find and return the title of the lesson only with \"# \" prefixed, without anything else." + - "Statement: Find and return the title of the lesson only with \"# \" string prefixed, without anything else." template_ix: 0 + rsp_begin_tag: "[TEACHING_PLAN_BEGIN]" + rsp_end_tag: "[TEACHING_PLAN_END]" - name: "" topic: "Teaching Hours" language: "Chinese" From b786bce4b9daf573f8f5eb4adc78f9f453ea3e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 14:54:30 +0800 Subject: [PATCH 35/41] fixbug: empty string causes aiofiles.open exepctition --- examples/fork_meta_role_write_teaching_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fork_meta_role_write_teaching_plan.py b/examples/fork_meta_role_write_teaching_plan.py index 21e3b5f7c..d1a6e0070 100644 --- a/examples/fork_meta_role_write_teaching_plan.py +++ b/examples/fork_meta_role_write_teaching_plan.py @@ -73,7 +73,7 @@ async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, * """ lesson = "" - if lesson_file is not None and Path(lesson_file).exists(): + if lesson_file and Path(lesson_file).exists(): async with aiofiles.open(lesson_file, mode="r", encoding="utf-8") as reader: lesson = await reader.read() logger.info(f"Course content: {lesson}") From 13a91349f78322a7f3df87e36b266ed501398c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 16:37:29 +0800 Subject: [PATCH 36/41] fixbug: cannot find metagpt module --- examples/llm_hello_world.py | 5 ++++- examples/search_google.py | 5 ++++- examples/search_kb.py | 5 ++++- examples/search_with_specific_engine.py | 9 ++++++++- examples/write_teaching_plan.py | 6 +++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/examples/llm_hello_world.py b/examples/llm_hello_world.py index 3ba03eea0..329247afc 100644 --- a/examples/llm_hello_world.py +++ b/examples/llm_hello_world.py @@ -4,9 +4,12 @@ @Time : 2023/5/6 14:13 @Author : alexanderwu @File : llm_hello_world.py +@Modified By: mashenquan, 2023-8-9, fix-bug: cannot find metagpt module. """ import asyncio - +from pathlib import Path +import sys +sys.path.append(str(Path(__file__).resolve().parent.parent)) from metagpt.llm import LLM, Claude from metagpt.logs import logger diff --git a/examples/search_google.py b/examples/search_google.py index 9e9521b9c..df45c29ea 100644 --- a/examples/search_google.py +++ b/examples/search_google.py @@ -4,10 +4,13 @@ @Time : 2023/5/7 18:32 @Author : alexanderwu @File : search_google.py +@Modified By: mashenquan, 2023-8-9, fix-bug: cannot find metagpt module. """ import asyncio - +from pathlib import Path +import sys +sys.path.append(str(Path(__file__).resolve().parent.parent)) from metagpt.roles import Searcher diff --git a/examples/search_kb.py b/examples/search_kb.py index b6f7d87a0..449099380 100644 --- a/examples/search_kb.py +++ b/examples/search_kb.py @@ -2,9 +2,12 @@ # -*- coding: utf-8 -*- """ @File : search_kb.py +@Modified By: mashenquan, 2023-8-9, fix-bug: cannot find metagpt module. """ import asyncio - +from pathlib import Path +import sys +sys.path.append(str(Path(__file__).resolve().parent.parent)) from metagpt.const import DATA_PATH from metagpt.document_store import FaissStore from metagpt.logs import logger diff --git a/examples/search_with_specific_engine.py b/examples/search_with_specific_engine.py index 7cc431cd4..4423011e4 100644 --- a/examples/search_with_specific_engine.py +++ b/examples/search_with_specific_engine.py @@ -1,5 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Modified By: mashenquan, 2023-8-9, fix-bug: cannot find metagpt module. +""" import asyncio - +from pathlib import Path +import sys +sys.path.append(str(Path(__file__).resolve().parent.parent)) from metagpt.roles import Searcher from metagpt.tools import SearchEngineType diff --git a/examples/write_teaching_plan.py b/examples/write_teaching_plan.py index 86125f090..5ab7d3ab5 100644 --- a/examples/write_teaching_plan.py +++ b/examples/write_teaching_plan.py @@ -1,11 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -@Modified By: mashenquan, 2023-07-27, + `industry` concept +@Modified By: mashenquan, 2023-07-27, + write teaching plan flow demo + """ import asyncio from pathlib import Path +import sys + +sys.path.append(str(Path(__file__).resolve().parent.parent)) import aiofiles import fire from metagpt.logs import logger From 63678de181096b73f6680b36bcc97e38c4dca113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 9 Aug 2023 20:48:25 +0800 Subject: [PATCH 37/41] feat: add more text formatting options --- metagpt/actions/azure_tts.py | 42 +++++++++++++++++++------ tests/metagpt/actions/test_azure_tts.py | 30 +++++++++++++++--- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/metagpt/actions/azure_tts.py b/metagpt/actions/azure_tts.py index f528ba001..3520de8b4 100644 --- a/metagpt/actions/azure_tts.py +++ b/metagpt/actions/azure_tts.py @@ -4,11 +4,13 @@ @Time : 2023/6/9 22:22 @Author : Leo Xiao @File : azure_tts.py +@Modified By: mashenquan, 2023-8-9, add more text formatting options """ from azure.cognitiveservices.speech import AudioConfig, SpeechConfig, SpeechSynthesizer from metagpt.actions.action import Action from metagpt.config import Config +from metagpt.const import WORKSPACE_ROOT class AzureTTS(Action): @@ -17,7 +19,7 @@ class AzureTTS(Action): self.config = Config() # 参数参考:https://learn.microsoft.com/zh-cn/azure/cognitive-services/speech-service/language-support?tabs=tts#voice-styles-and-roles - def synthesize_speech(self, lang, voice, role, text, output_file): + def synthesize_speech(self, lang, voice, text, output_file): subscription_key = self.config.get('AZURE_TTS_SUBSCRIPTION_KEY') region = self.config.get('AZURE_TTS_REGION') speech_config = SpeechConfig( @@ -29,25 +31,47 @@ class AzureTTS(Action): speech_config=speech_config, audio_config=audio_config) - # if voice=="zh-CN-YunxiNeural": + # More detail: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup-voice ssml_string = f""" - - {text} - + {text} """ - synthesizer.speak_ssml_async(ssml_string).get() + return synthesizer.speak_ssml_async(ssml_string).get() + @staticmethod + def role_style_text(role, style, text): + return f'{text}' + + @staticmethod + def role_text(role, text): + return f'{text}' + + @staticmethod + def style_text(style, text): + return f'{text}' if __name__ == "__main__": azure_tts = AzureTTS("azure_tts") + text = """ + 女儿看见父亲走了进来,问道: + + “您来的挺快的,怎么过来的?” + + 父亲放下手提包,说: + + “刚打车过来的,路上还挺顺畅。” + + """ + path = WORKSPACE_ROOT / "tts" + path.mkdir(exist_ok=True, parents=True) + filename = path / "output.wav" azure_tts.synthesize_speech( "zh-CN", "zh-CN-YunxiNeural", - "Boy", - "你好,我是卡卡", - "output.wav") + text=AzureTTS.role_style_text(role="Boy", style="affectionate", text="你好,我是卡卡"), + output_file=str(filename) + ) diff --git a/tests/metagpt/actions/test_azure_tts.py b/tests/metagpt/actions/test_azure_tts.py index b5a333af2..2145f7133 100644 --- a/tests/metagpt/actions/test_azure_tts.py +++ b/tests/metagpt/actions/test_azure_tts.py @@ -4,18 +4,38 @@ @Time : 2023/7/1 22:50 @Author : alexanderwu @File : test_azure_tts.py +@Modified By: mashenquan, 2023-8-9, add more text formatting options """ from metagpt.actions.azure_tts import AzureTTS +from metagpt.const import WORKSPACE_ROOT def test_azure_tts(): azure_tts = AzureTTS("azure_tts") - azure_tts.synthesize_speech( + text = """ + 女儿看见父亲走了进来,问道: + + “您来的挺快的,怎么过来的?” + + 父亲放下手提包,说: + + “Writing a binary file in Python is similar to writing a regular text file, but you'll work with bytes instead of strings.” + + """ + path = WORKSPACE_ROOT / "tts" + path.mkdir(exist_ok=True, parents=True) + filename = path / "girl.wav" + result = azure_tts.synthesize_speech( "zh-CN", - "zh-CN-YunxiNeural", - "Boy", - "你好,我是卡卡", - "output.wav") + "zh-CN-XiaomoNeural", + text=text, + output_file=str(filename)) + + print(result) # 运行需要先配置 SUBSCRIPTION_KEY # TODO: 这里如果要检验,还要额外加上对应的asr,才能确保前后生成是接近一致的,但现在还没有 + + +if __name__ == '__main__': + test_azure_tts() From 866c5bcb15b1e51af743272892e38e9e3795d5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 18 Aug 2023 15:10:23 +0800 Subject: [PATCH 38/41] fixbug: merge bug --- metagpt/provider/openai_api.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 0f7100db8..88343373f 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -25,26 +25,6 @@ from metagpt.utils.token_counter import ( ) -<<<<<<< HEAD -def retry(max_retries): - def decorator(f): - @wraps(f) - async def wrapper(*args, **kwargs): - for i in range(max_retries): - try: - return await f(*args, **kwargs) - except Exception as e: - error_str = traceback.format_exc() - logger.warning(f"Exception occurred: {str(e)}, stack:{error_str}. Retrying...") - if i == max_retries - 1: - raise - await asyncio.sleep(2 ** i) - return wrapper - return decorator - - -======= ->>>>>>> main class RateLimiter: """Rate control class, each call goes through wait_if_needed, sleep if rate control is needed""" From 321f4a5a17bae23be5360f2ba7d2fadb76e66f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 18 Aug 2023 15:11:35 +0800 Subject: [PATCH 39/41] fixbug: merge bug --- metagpt/provider/openai_api.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 0f7100db8..88343373f 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -25,26 +25,6 @@ from metagpt.utils.token_counter import ( ) -<<<<<<< HEAD -def retry(max_retries): - def decorator(f): - @wraps(f) - async def wrapper(*args, **kwargs): - for i in range(max_retries): - try: - return await f(*args, **kwargs) - except Exception as e: - error_str = traceback.format_exc() - logger.warning(f"Exception occurred: {str(e)}, stack:{error_str}. Retrying...") - if i == max_retries - 1: - raise - await asyncio.sleep(2 ** i) - return wrapper - return decorator - - -======= ->>>>>>> main class RateLimiter: """Rate control class, each call goes through wait_if_needed, sleep if rate control is needed""" From ae94b6dff8e10cb65450bd05a8acf14ee24a169d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sun, 20 Aug 2023 20:22:59 +0800 Subject: [PATCH 40/41] feat: merge role_option --- metagpt/actions/write_teaching_plan.py | 4 ++-- metagpt/roles/fork_meta_role.py | 11 +++++++---- metagpt/roles/role.py | 1 + metagpt/roles/teacher.py | 6 +++--- .../metagpt/actions/test_write_teaching_plan.py | 8 +++++--- tests/metagpt/roles/test_fork_meta_role.py | 6 +++++- tests/metagpt/roles/test_teacher.py | 17 +++++++++++------ 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 3718c9801..53371b5a1 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -20,7 +20,7 @@ class TeachingPlanRequirement(Action): class WriteTeachingPlanPart(Action): """Write Teaching Plan Part""" - def __init__(self, name: str = "", context=None, llm=None, topic: str = "", language: str = "Chinese"): + def __init__(self, options, name: str = "", context=None, llm=None, topic: str = "", language: str = "Chinese"): """ :param name: action name @@ -29,7 +29,7 @@ class WriteTeachingPlanPart(Action): :param topic: topic part of teaching plan :param language: A human language, such as Chinese, English, French, etc. """ - super().__init__(name, context, llm) + super().__init__(options, name, context, llm) self.topic = topic self.language = language self.rsp = None diff --git a/metagpt/roles/fork_meta_role.py b/metagpt/roles/fork_meta_role.py index 555bc8cf3..c21d08e37 100644 --- a/metagpt/roles/fork_meta_role.py +++ b/metagpt/roles/fork_meta_role.py @@ -26,14 +26,16 @@ from metagpt.schema import Message class ForkMetaRole(Role): """A `fork` style meta role capable of generating arbitrary roles at runtime based on a configuration file""" - def __init__(self, options, **kwargs): + def __init__(self, runtime_options, cost_manager, role_options, **kwargs): """Initialize a `fork` style meta role - :param options: pattern yaml file data + :param runtime_options: System configuration + :param cost_manager: Cost manager + :param role_options: pattern yaml file data :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` :param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2` """ - opts = UMLMetaRoleOptions(**options) + opts = UMLMetaRoleOptions(**role_options) global_variables = { "name": Role.format_value(opts.name, kwargs), "profile": Role.format_value(opts.profile, kwargs), @@ -47,6 +49,8 @@ class ForkMetaRole(Role): global_variables[k] = v super(ForkMetaRole, self).__init__( + options=runtime_options, + cost_manager=cost_manager, name=global_variables["name"], profile=global_variables["profile"], goal=global_variables["goal"], @@ -54,7 +58,6 @@ class ForkMetaRole(Role): desc=global_variables["desc"], **kwargs ) - self.options = options actions = [] for m in opts.actions: for k, v in m.items(): diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 5397893eb..00f8ed45f 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -173,6 +173,7 @@ class Role: """Return number of action""" return len(self._actions) + @property def options(self): return self._options diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index 24ede7402..f29f384db 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -20,13 +20,13 @@ import re class Teacher(Role): """Support configurable teacher roles, with native and teaching languages being replaceable through configurations.""" - def __init__(self, name='Lily', profile='{teaching_language} Teacher', + def __init__(self, options, name='Lily', profile='{teaching_language} Teacher', goal='writing a {language} teaching plan part by part', constraints='writing in {language}', desc="", *args, **kwargs): - super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs) + super().__init__(options=options, name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs) actions = [] for topic in WriteTeachingPlanPart.TOPICS: - act = WriteTeachingPlanPart(topic=topic, llm=self._llm) + act = WriteTeachingPlanPart(options=options, topic=topic, llm=self._llm) actions.append(act) self._init_actions(actions) self._watch({TeachingPlanRequirement}) diff --git a/tests/metagpt/actions/test_write_teaching_plan.py b/tests/metagpt/actions/test_write_teaching_plan.py index 299a89639..6754fe88c 100644 --- a/tests/metagpt/actions/test_write_teaching_plan.py +++ b/tests/metagpt/actions/test_write_teaching_plan.py @@ -12,12 +12,13 @@ from pydantic import BaseModel from langchain.llms.base import LLM from metagpt.actions.write_teaching_plan import WriteTeachingPlanPart +from metagpt.config import Config from metagpt.schema import Message class MockWriteTeachingPlanPart(WriteTeachingPlanPart): - def __init__(self, name: str = '', context=None, llm: LLM = None, topic="", language="Chinese"): - super().__init__(name, context, llm, topic, language) + def __init__(self, options, name: str = '', context=None, llm: LLM = None, topic="", language="Chinese"): + super().__init__(options, name, context, llm, topic, language) async def _aask(self, prompt: str, system_msgs: Optional[list[str]] = None) -> str: return f"{WriteTeachingPlanPart.DATA_BEGIN_TAG}\nprompt\n{WriteTeachingPlanPart.DATA_END_TAG}" @@ -47,7 +48,8 @@ async def mock_write_teaching_plan_part(): for i in inputs: seed = Inputs(**i) - act = MockWriteTeachingPlanPart(name=seed.name, topic=seed.topic, language=seed.language) + options = Config().runtime_options + act = MockWriteTeachingPlanPart(options=options, name=seed.name, topic=seed.topic, language=seed.language) await act.run([Message(content="")]) assert act.topic == seed.topic assert str(act) == seed.topic diff --git a/tests/metagpt/roles/test_fork_meta_role.py b/tests/metagpt/roles/test_fork_meta_role.py index b2659330d..355197234 100644 --- a/tests/metagpt/roles/test_fork_meta_role.py +++ b/tests/metagpt/roles/test_fork_meta_role.py @@ -9,6 +9,8 @@ from typing import Dict from pydantic import BaseModel +from metagpt.config import Config +from metagpt.provider.openai_api import CostManager from metagpt.roles.fork_meta_role import ForkMetaRole @@ -79,7 +81,9 @@ def test_creat_role(): "teaching_language": "AA", "language": "BB" } - role = ForkMetaRole(seed.role, **kwargs) + runtime_options = Config().runtime_options + cost_manager = CostManager(options=runtime_options) + role = ForkMetaRole(runtime_options=runtime_options, cost_manager=cost_manager, role_options=seed.role, **kwargs) assert role.action_count == 2 assert "{" not in role.profile assert "{" not in role.goal diff --git a/tests/metagpt/roles/test_teacher.py b/tests/metagpt/roles/test_teacher.py index 5faa43455..11c268edb 100644 --- a/tests/metagpt/roles/test_teacher.py +++ b/tests/metagpt/roles/test_teacher.py @@ -9,6 +9,8 @@ from typing import Dict, Optional from pydantic import BaseModel +from metagpt.config import Config +from metagpt.provider.openai_api import CostManager from metagpt.roles.teacher import Teacher @@ -42,22 +44,25 @@ def test_init(): }, { "name": "Lily{language}", - "expect_name": "LilyChinese", + "expect_name": "Lily{language}", "profile": "X {teaching_language}", - "expect_profile": "X English", + "expect_profile": "X {teaching_language}", "goal": "Do {something_big}, {language}", - "expect_goal": "Do {something_big}, Chinese", + "expect_goal": "Do {something_big}, {language}", "constraints": "Do in {key1}, {language}", - "expect_constraints": "Do in {key1}, Chinese", + "expect_constraints": "Do in {key1}, {language}", "kwargs": {}, "desc": "aaa{language}", - "expect_desc": "aaaChinese" + "expect_desc": "aaa{language}" }, ] for i in inputs: seed = Inputs(**i) - teacher = Teacher(name=seed.name, profile=seed.profile, goal=seed.goal, constraints=seed.constraints, + options = Config().runtime_options + cost_manager = CostManager(options=options) + teacher = Teacher(options=options, cost_manager=cost_manager, name=seed.name, profile=seed.profile, + goal=seed.goal, constraints=seed.constraints, desc=seed.desc, **seed.kwargs) assert teacher.name == seed.expect_name assert teacher.desc == seed.expect_desc From 86e0e706191dc8822d1ed183108fc6546175d16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 21 Aug 2023 10:44:40 +0800 Subject: [PATCH 41/41] fixbug: teacher role --- .../fork_meta_role_write_teaching_plan.py | 5 +++- examples/write_teaching_plan.py | 2 +- metagpt/actions/meta_action.py | 25 +++++++++++-------- metagpt/roles/fork_meta_role.py | 8 +++--- metagpt/roles/uml_meta_role_factory.py | 2 +- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/examples/fork_meta_role_write_teaching_plan.py b/examples/fork_meta_role_write_teaching_plan.py index d2898605e..e529a9b46 100644 --- a/examples/fork_meta_role_write_teaching_plan.py +++ b/examples/fork_meta_role_write_teaching_plan.py @@ -90,8 +90,11 @@ async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, * configs = yaml.safe_load(reader) startup_config = ProjectConfig(**configs) - roles = UMLMetaRoleFactory.create_roles(startup_config.roles, **kwargs) company = SoftwareCompany() + roles = UMLMetaRoleFactory.create_roles(role_configs=startup_config.roles, + options=company.options, + cost_manager=company.cost_manager, + **kwargs) company.hire(roles) company.invest(startup_config.startup.investment) company.start_project(lesson, role=startup_config.startup.role, diff --git a/examples/write_teaching_plan.py b/examples/write_teaching_plan.py index 9874d10a5..6ab5edce4 100644 --- a/examples/write_teaching_plan.py +++ b/examples/write_teaching_plan.py @@ -77,7 +77,7 @@ async def startup(lesson_file: str, investment: float = 3.0, n_round: int = 1, * lesson = demo_lesson company = SoftwareCompany() - company.hire([Teacher(*args, **kwargs)]) + company.hire([Teacher(options=company.options, cost_manager=company.cost_manager, *args, **kwargs)]) company.invest(investment) company.start_project(lesson, role="Teacher", cause_by=TeachingPlanRequirement) await company.run(n_round=1) diff --git a/metagpt/actions/meta_action.py b/metagpt/actions/meta_action.py index 3f01b8c0f..4c52e7cfd 100644 --- a/metagpt/actions/meta_action.py +++ b/metagpt/actions/meta_action.py @@ -21,19 +21,22 @@ from metagpt.schema import Message class MetaAction(Action): - def __init__(self, options: MetaActionOptions, llm=None, **kwargs): - super(MetaAction, self).__init__(options.name, kwargs.get("context"), llm=llm) - self.prompt = options.format_prompt(**kwargs) - self.options = options + def __init__(self, options, action_options: MetaActionOptions, llm=None, **kwargs): + super(MetaAction, self).__init__(options=options, + name=action_options.name, + context=kwargs.get("context"), + llm=llm) + self.prompt = action_options.format_prompt(**kwargs) + self.action_options = action_options self.kwargs = kwargs def __str__(self): """Return `topic` value when str()""" - return self.options.topic + return self.action_options.topic def __repr__(self): """Show `topic` value when debug""" - return self.options.topic + return self.action_options.topic async def run(self, messages, *args, **kwargs): if len(messages) < 1 or not isinstance(messages[0], Message): @@ -46,11 +49,11 @@ class MetaAction(Action): return self.rsp def _set_result(self, rsp): - if self.options.rsp_begin_tag and self.options.rsp_begin_tag in rsp: - ix = rsp.index(self.options.rsp_begin_tag) - rsp = rsp[ix + len(self.options.rsp_begin_tag):] - if self.options.rsp_end_tag and self.options.rsp_end_tag in rsp: - ix = rsp.index(self.options.rsp_end_tag) + if self.action_options.rsp_begin_tag and self.action_options.rsp_begin_tag in rsp: + ix = rsp.index(self.action_options.rsp_begin_tag) + rsp = rsp[ix + len(self.action_options.rsp_begin_tag):] + if self.action_options.rsp_end_tag and self.action_options.rsp_end_tag in rsp: + ix = rsp.index(self.action_options.rsp_end_tag) rsp = rsp[0:ix] self.rsp = rsp.strip() diff --git a/metagpt/roles/fork_meta_role.py b/metagpt/roles/fork_meta_role.py index c21d08e37..5311bc4f0 100644 --- a/metagpt/roles/fork_meta_role.py +++ b/metagpt/roles/fork_meta_role.py @@ -26,10 +26,10 @@ from metagpt.schema import Message class ForkMetaRole(Role): """A `fork` style meta role capable of generating arbitrary roles at runtime based on a configuration file""" - def __init__(self, runtime_options, cost_manager, role_options, **kwargs): + def __init__(self, options, cost_manager, role_options, **kwargs): """Initialize a `fork` style meta role - :param runtime_options: System configuration + :param options: System configuration :param cost_manager: Cost manager :param role_options: pattern yaml file data :param args: Parameters passed in format: `python your_script.py arg1 arg2 arg3` @@ -49,7 +49,7 @@ class ForkMetaRole(Role): global_variables[k] = v super(ForkMetaRole, self).__init__( - options=runtime_options, + options=options, cost_manager=cost_manager, name=global_variables["name"], profile=global_variables["profile"], @@ -70,7 +70,7 @@ class ForkMetaRole(Role): o = MetaActionOptions(**m) o.set_default_template(opts.templates[o.template_ix]) - act = MetaAction(options=o, llm=self._llm, **m) + act = MetaAction(options=options, action_options=o, llm=self._llm, **m) actions.append(act) self._init_actions(actions) requirement_types = set() diff --git a/metagpt/roles/uml_meta_role_factory.py b/metagpt/roles/uml_meta_role_factory.py index 78f9689a2..42071b0a6 100644 --- a/metagpt/roles/uml_meta_role_factory.py +++ b/metagpt/roles/uml_meta_role_factory.py @@ -33,7 +33,7 @@ class UMLMetaRoleFactory: raise NotImplementedError( f"{opt.role_type} is not implemented" ) - r = constructor(m, **kwargs) + r = constructor(role_options=m, **kwargs) roles.append(r) return roles