mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-23 15:48:11 +02:00
feat: merge teacher
This commit is contained in:
commit
01c487fb6a
32 changed files with 1578 additions and 38 deletions
|
|
@ -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)
|
||||
|
|
|
|||
64
metagpt/actions/meta_action.py
Normal file
64
metagpt/actions/meta_action.py
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
159
metagpt/actions/write_teaching_plan.py
Normal file
159
metagpt/actions/write_teaching_plan.py
Normal 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]"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
130
metagpt/roles/fork_meta_role.py
Normal file
130
metagpt/roles/fork_meta_role.py
Normal 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)
|
||||
|
|
@ -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
97
metagpt/roles/teacher.py
Normal 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
|
||||
43
metagpt/roles/uml_meta_role_factory.py
Normal file
43
metagpt/roles/uml_meta_role_factory.py
Normal 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..
|
||||
}
|
||||
69
metagpt/roles/uml_meta_role_options.py
Normal file
69
metagpt/roles/uml_meta_role_options.py
Normal 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
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue