feat: merge teacher

This commit is contained in:
莘权 马 2023-08-22 22:34:39 +08:00
commit 01c487fb6a
32 changed files with 1578 additions and 38 deletions

View file

@ -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.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
import shutil
@ -136,7 +137,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)

View file

@ -0,0 +1,64 @@
#!/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, 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.action_options.topic
def __repr__(self):
"""Show `topic` value when debug"""
return self.action_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.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()
@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

View file

@ -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.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from typing import List, Tuple
@ -116,7 +117,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)

View file

@ -0,0 +1,159 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/7/27
@Author : mashenquan
@File : write_teaching_plan.py
"""
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, options, name: str = "", context=None, llm=None, topic: str = "", language: str = "Chinese"):
"""
: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__(options, name, context, llm)
self.topic = topic
self.language = language
self.rsp = None
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, [])
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,
statements="\n".join(statements),
lesson=messages[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):
"""Return `topic` value when str()"""
return self.topic
def __repr__(self):
"""Show `topic` value when debug"""
return self.topic
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",
"Teaching Time Allocation", "Assessment and Feedback", "Teaching Summary and Improvement",
"Vocabulary Cloze", "Choice Questions", "Grammar Questions", "Translation Questions"
]
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."
],
"Vocabulary Cloze": [
"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.",
],
"Grammar Questions": [
"Statement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", "
"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. The translation should include 10 {language} questions with "
"{teaching_language} answers, and it should also include 10 {teaching_language} questions with "
"{language} answers."
]
}
# 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]"

View file

@ -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

View file

@ -8,8 +8,9 @@
"""
import asyncio
import time
from typing import NamedTuple, Dict
from typing import NamedTuple
import traceback
import openai
from openai.error import APIConnectionError
from pydantic import BaseModel
@ -150,7 +151,15 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter):
self.rpm = int(self._options.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 = []

View file

@ -0,0 +1,130 @@
#!/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 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
class ForkMetaRole(Role):
"""A `fork` style meta role capable of generating arbitrary roles at runtime based on a configuration file"""
def __init__(self, options, cost_manager, role_options, **kwargs):
"""Initialize a `fork` style meta role
: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`
:param kwargs: Parameters passed in format: `python your_script.py --param1=value1 --param2=value2`
"""
opts = UMLMetaRoleOptions(**role_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
super(ForkMetaRole, self).__init__(
options=options,
cost_manager=cost_manager,
name=global_variables["name"],
profile=global_variables["profile"],
goal=global_variables["goal"],
constraints=global_variables["constraints"],
desc=global_variables["desc"],
**kwargs
)
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
o = MetaActionOptions(**m)
o.set_default_template(opts.templates[o.template_ix])
act = MetaAction(options=options, action_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)
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"""
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}")
@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)

View file

@ -4,6 +4,7 @@
@Time : 2023/5/11 14:42
@Author : alexanderwu
@File : role.py
@Modified By: mashenquan, 2023-8-7, :class:`Role` + properties.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
@ -47,7 +48,7 @@ ROLE_TEMPLATE = """Your response should be based on the previous conversation hi
class RoleSetting(BaseModel):
"""角色设定"""
"""Role properties"""
name: str
profile: str
goal: str
@ -62,7 +63,7 @@ class RoleSetting(BaseModel):
class RoleContext(BaseModel):
"""角色运行时上下文"""
"""Runtime role context"""
env: 'Environment' = Field(default=None)
memory: Memory = Field(default_factory=Memory)
long_term_memory: LongTermMemory = Field(default_factory=LongTermMemory)
@ -82,7 +83,7 @@ class RoleContext(BaseModel):
@property
def important_memory(self) -> list[Message]:
"""获得关注动作对应的信息"""
"""Retrieve information corresponding to the attention action."""
return self.memory.get_by_actions(self.watch)
@property
@ -91,17 +92,25 @@ class RoleContext(BaseModel):
class Role:
"""角色/代理"""
"""Role/Proxy"""
def __init__(self, options, cost_manager, name="", profile="", goal="", constraints="", desc="", *args, **kwargs):
self._options = Role.supply_options(options=kwargs, default_options=options)
name = Role.format_value(name, self._options)
profile = Role.format_value(profile, self._options)
goal = Role.format_value(goal, self._options)
constraints = Role.format_value(constraints, self._options)
desc = Role.format_value(desc, self._options)
def __init__(self, options, cost_manager, name="", profile="", goal="", constraints="", desc=""):
self._options = options if options else {}
self._cost_manager = cost_manager
self._llm = LLM(options=self._options, cost_manager=cost_manager)
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(options=options)
self._rc = RoleContext(options=self._options)
def _reset(self):
self._states = []
@ -139,6 +148,31 @@ 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
@property
def action_count(self):
"""Return number of action"""
return len(self._actions)
@property
def options(self):
return self._options
@ -175,7 +209,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, **self._options)
# logger.info(response)
if isinstance(response, ActionOutput):
msg = Message(content=response.content, instruct_content=response.instruct_content,
@ -192,9 +227,9 @@ class Role:
if not self._rc.env:
return 0
env_msgs = self._rc.env.memory.get()
observed = self._rc.env.memory.get_by_actions(self._rc.watch)
self._rc.news = self._rc.memory.remember(observed) # remember recent exact or similar memories
for i in env_msgs:
@ -251,3 +286,30 @@ class Role:
# 将回复发布到环境,等待下一个订阅者处理
self._publish_message(rsp)
return rsp
@staticmethod
def supply_options(options, default_options=None):
"""Supply missing options"""
ret = default_options.copy() if default_options else {}
if not options:
return ret
ret.update(options)
return ret
@staticmethod
def format_value(value, opts, default_opts=None):
"""Fill parameters inside `value` with `options`."""
if not isinstance(value, str):
return value
if "{" not in value:
return value
merged_opts = Role.supply_options(opts, default_opts)
try:
return value.format(**merged_opts)
except KeyError as e:
logger.warning(f"Parameter is missing:{e}")
for k, v in merged_opts.items():
value = value.replace("{" + f"{k}" + "}", str(v))
return value

97
metagpt/roles/teacher.py Normal file
View file

@ -0,0 +1,97 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/7/27
@Author : mashenquan
@File : teacher.py
"""
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
import re
class Teacher(Role):
"""Support configurable teacher roles,
with native and teaching languages being replaceable through configurations."""
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__(options=options, name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs)
actions = []
for topic in WriteTeachingPlanPart.TOPICS:
act = WriteTeachingPlanPart(options=options, 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 = 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}")
@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)
@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

View file

@ -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(role_options=m, **kwargs)
roles.append(r)
return roles
CONSTRUCTORS = {
"fork": ForkMetaRole,
# TODO: add more activity node constructor here..
}

View file

@ -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

View file

@ -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()`.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
@ -49,10 +50,10 @@ class SoftwareCompany(BaseModel):
if self.total_cost > self.max_budget:
raise NoMoneyException(self.total_cost, f'Insufficient funds: {self.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())