From a7a1195a31c011bb624e6f643fc70c7ad644527c Mon Sep 17 00:00:00 2001 From: geekan Date: Fri, 22 Dec 2023 17:15:36 +0800 Subject: [PATCH] fix bugs and make it perform better --- examples/agent_creator.py | 2 +- examples/build_customized_agent.py | 4 +-- examples/build_customized_multi_agents.py | 2 +- examples/debate.py | 2 +- examples/debate_simple.py | 14 ++++++---- metagpt/actions/action.py | 5 ++-- metagpt/actions/action_node.py | 34 +++++++++++++++++------ metagpt/environment.py | 11 +++++--- metagpt/roles/researcher.py | 2 +- metagpt/roles/role.py | 13 ++++++--- metagpt/roles/searcher.py | 2 +- 11 files changed, 59 insertions(+), 32 deletions(-) diff --git a/examples/agent_creator.py b/examples/agent_creator.py index 0b85b33a6..a23c31f16 100644 --- a/examples/agent_creator.py +++ b/examples/agent_creator.py @@ -64,7 +64,7 @@ class AgentCreator(Role): self._init_actions([CreateAgent]) async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") todo = self._rc.todo msg = self._rc.memory.get()[-1] diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py index 679aee948..bceeb24fc 100644 --- a/examples/build_customized_agent.py +++ b/examples/build_customized_agent.py @@ -60,7 +60,7 @@ class SimpleCoder(Role): self._init_actions([SimpleWriteCode]) async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") todo = self._rc.todo # todo will be SimpleWriteCode() msg = self.get_memories(k=1)[0] # find the most recent messages @@ -80,7 +80,7 @@ class RunnableCoder(Role): self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value) async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") # By choosing the Action by order under the hood # todo will be first SimpleWriteCode() then SimpleRunCode() todo = self._rc.todo diff --git a/examples/build_customized_multi_agents.py b/examples/build_customized_multi_agents.py index 518aa6324..b8e01b486 100644 --- a/examples/build_customized_multi_agents.py +++ b/examples/build_customized_multi_agents.py @@ -80,7 +80,7 @@ class SimpleTester(Role): self._watch([SimpleWriteCode, SimpleWriteReview]) # feel free to try this too async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") todo = self._rc.todo # context = self.get_memories(k=1)[0].content # use the most recent memory as context diff --git a/examples/debate.py b/examples/debate.py index 52f49e00e..ba15abda8 100644 --- a/examples/debate.py +++ b/examples/debate.py @@ -63,7 +63,7 @@ class Debator(Role): return len(self._rc.news) async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") todo = self._rc.todo # An instance of SpeakAloud memories = self.get_memories() diff --git a/examples/debate_simple.py b/examples/debate_simple.py index 0a86c4131..b90af4f82 100644 --- a/examples/debate_simple.py +++ b/examples/debate_simple.py @@ -8,13 +8,15 @@ import asyncio from metagpt.actions import Action, UserRequirement +from metagpt.environment import Environment from metagpt.roles import Role from metagpt.team import Team -action1 = Action(name="BidenSay", instruction="Use diverse words to attack your opponent, strong and emotional.") -action2 = Action(name="TrumpSay", instruction="Use diverse words to attack your opponent, strong and emotional.") -biden = Role(name="Biden", profile="democrat", goal="win election", actions=[action1], watch=[action2, UserRequirement]) -trump = Role(name="Trump", profile="republican", goal="win election", actions=[action2], watch=[action1]) -team = Team(investment=10.0, env_desc="US election live broadcast", roles=[biden, trump]) +action1 = Action(name="BidenSay", instruction="发表政见,充满激情的与对手辩论") +action2 = Action(name="TrumpSay", instruction="发表政见,充满激情的与对手辩论,MAGA!") +biden = Role(name="拜登", profile="民主党", goal="大选获胜", actions=[action1], watch=[action2, UserRequirement]) +trump = Role(name="特朗普", profile="共和党", goal="大选获胜", actions=[action2], watch=[action1]) +env = Environment(desc="US election live broadcast") +team = Team(investment=10.0, env=env, roles=[biden, trump]) -asyncio.run(team.run(idea="Topic: climate change", n_round=5)) +asyncio.run(team.run(idea="主题:气候变化,用中文辩论", n_round=5)) diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index f0470640d..24237c6f1 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -41,7 +41,7 @@ class Action(BaseModel): def __init_with_instruction(self, instruction: str): """Initialize action with instruction""" - self.node = ActionNode(key=self.name, expected_type=str, instruction=instruction, example="") + self.node = ActionNode(key=self.name, expected_type=str, instruction=instruction, example="", schema="raw") return self def __init__(self, **kwargs: Any): @@ -85,7 +85,8 @@ class Action(BaseModel): async def _run_action_node(self, *args, **kwargs): """Run action node""" msgs = args[0] - context = "\n".join([f"Msg {idx}: {i}" for idx, i in enumerate(reversed(msgs))]) + context = "## History Messages\n" + context += "\n".join([f"{idx}: {i}" for idx, i in enumerate(reversed(msgs))]) return await self.node.fill(context=context, llm=self.llm) async def run(self, *args, **kwargs): diff --git a/metagpt/actions/action_node.py b/metagpt/actions/action_node.py index 795634a17..7445e5000 100644 --- a/metagpt/actions/action_node.py +++ b/metagpt/actions/action_node.py @@ -21,7 +21,7 @@ from metagpt.utils.common import OutputParser, general_after_log TAG = "CONTENT" -LANGUAGE_CONSTRAINT = "Language: Please use the same language as the user input." +LANGUAGE_CONSTRAINT = "Language: Please use the same language as Human INPUT." FORMAT_CONSTRAINT = f"Format: output wrapped inside [{TAG}][/{TAG}] like format example, nothing else." @@ -55,7 +55,7 @@ def dict_to_markdown(d, prefix="- ", kv_sep="\n", postfix="\n"): class ActionNode: """ActionNode is a tree of nodes.""" - mode: str + schema: str # raw/json/markdown, default: "" # Action Context context: str # all the context, including all necessary info @@ -81,6 +81,7 @@ class ActionNode: example: Any, content: str = "", children: dict[str, "ActionNode"] = None, + schema: str = "", ): self.key = key self.expected_type = expected_type @@ -88,6 +89,7 @@ class ActionNode: self.example = example self.content = content self.children = children if children is not None else {} + self.schema = schema def __str__(self): return ( @@ -222,7 +224,13 @@ class ActionNode: mode="children": 编译所有子节点为一个统一模板,包括instruction与example mode="all": NotImplemented mode="root": NotImplemented + schmea: raw/json/markdown + schema="raw": 不编译,context, lang_constaint, instruction + schema="json":编译context, example(json), instruction(markdown), constraint, action + schema="markdown": 编译context, example(markdown), instruction(markdown), constraint, action """ + if schema == "raw": + return context + "\n\n## Actions\n" + LANGUAGE_CONSTRAINT + "\n" + self.instruction # FIXME: json instruction会带来格式问题,如:"Project name": "web_2048 # 项目名称使用下划线", # compile example暂时不支持markdown @@ -283,12 +291,17 @@ class ActionNode: async def simple_fill(self, schema, mode): prompt = self.compile(context=self.context, schema=schema, mode=mode) - mapping = self.get_mapping(mode) - class_name = f"{self.key}_AN" - content, scontent = await self._aask_v1(prompt, class_name, mapping, schema=schema) - self.content = content - self.instruct_content = scontent + if schema != "raw": + mapping = self.get_mapping(mode) + class_name = f"{self.key}_AN" + content, scontent = await self._aask_v1(prompt, class_name, mapping, schema=schema) + self.content = content + self.instruct_content = scontent + else: + self.content = await self.llm.aask(prompt) + self.instruct_content = None + return self async def fill(self, context, llm, schema="json", mode="auto", strgy="simple"): @@ -297,6 +310,7 @@ class ActionNode: :param context: Everything we should know when filling node. :param llm: Large Language Model with pre-defined system message. :param schema: json/markdown, determine example and output format. + - raw: free form text - json: it's easy to open source LLM with json format - markdown: when generating code, markdown is always better :param mode: auto/children/root @@ -310,14 +324,16 @@ class ActionNode: """ self.set_llm(llm) self.set_context(context) + if self.schema: + schema = self.schema if strgy == "simple": - return await self.simple_fill(schema, mode) + return await self.simple_fill(schema=schema, mode=mode) elif strgy == "complex": # 这里隐式假设了拥有children tmp = {} for _, i in self.children.items(): - child = await i.simple_fill(schema, mode) + child = await i.simple_fill(schema=schema, mode=mode) tmp.update(child.instruct_content.dict()) cls = self.create_children_class() self.instruct_content = cls(**tmp) diff --git a/metagpt/environment.py b/metagpt/environment.py index 319abc870..0ee85f707 100644 --- a/metagpt/environment.py +++ b/metagpt/environment.py @@ -96,15 +96,18 @@ class Environment(BaseModel): """增加一个在当前环境的角色 Add a role in the current environment """ - role.set_env(self) self.roles[role.profile] = role + role.set_env(self) def add_roles(self, roles: Iterable[Role]): """增加一批在当前环境的角色 Add a batch of characters in the current environment """ for role in roles: - self.add_role(role) + self.roles[role.profile] = role + + for role in roles: # setup system message with roles + role.set_env(self) def publish_message(self, message: Message, peekable: bool = True) -> bool: """ @@ -153,8 +156,8 @@ class Environment(BaseModel): """ return self.roles.get(name, None) - def role_names(self) -> str: - return ", ".join([f"{i.name}" for i in self.roles.values()]) + def role_names(self) -> list[str]: + return [i.name for i in self.roles.values()] @property def is_idle(self): diff --git a/metagpt/roles/researcher.py b/metagpt/roles/researcher.py index b7e61a4d6..f981d72a7 100644 --- a/metagpt/roles/researcher.py +++ b/metagpt/roles/researcher.py @@ -52,7 +52,7 @@ class Researcher(Role): return False async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})") todo = self._rc.todo msg = self._rc.memory.get(k=1)[0] if isinstance(msg.instruct_content, Report): diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 8b048a523..a90699e01 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -141,7 +141,7 @@ class Role(BaseModel): desc: str = "" is_human: bool = False - _llm: BaseGPTAPI = Field(default_factory=LLM) + _llm: BaseGPTAPI = Field(default_factory=LLM) # Each role has its own LLM, use different system message _role_id: str = "" _states: list[str] = [] _actions: list[Action] = [] @@ -259,6 +259,9 @@ class Role(BaseModel): def _init_action_system_message(self, action: Action): action.set_prefix(self._get_prefix()) + def refresh_system_message(self): + self._llm.system_prompt = self._get_prefix() + def set_recovered(self, recovered: bool = False): self.recovered = recovered @@ -340,6 +343,7 @@ class Role(BaseModel): self._rc.env = env if env: env.set_subscription(self, self._subscription) + self.refresh_system_message() # add env message to system message @property def subscription(self) -> Set: @@ -362,7 +366,8 @@ class Role(BaseModel): prefix += CONSTRAINT_TEMPLATE.format(**{"constraints": self.constraints}) if self._rc.env and self._rc.env.desc: - env_desc = f"You are in {self._rc.env.desc} with roles({self._rc.env.role_names()})." + other_role_names = ", ".join(self._rc.env.role_names()) + env_desc = f"You are in {self._rc.env.desc} with roles({other_role_names})." prefix += env_desc return prefix @@ -402,13 +407,13 @@ class Role(BaseModel): return True async def _act(self) -> Message: - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") response = await self._rc.todo.run(self._rc.important_memory) if isinstance(response, (ActionOutput, ActionNode)): msg = Message( content=response.content, instruct_content=response.instruct_content, - role=self.profile, + role=self._setting, cause_by=self._rc.todo, sent_from=self, ) diff --git a/metagpt/roles/searcher.py b/metagpt/roles/searcher.py index e4a672176..da844b4dc 100644 --- a/metagpt/roles/searcher.py +++ b/metagpt/roles/searcher.py @@ -57,7 +57,7 @@ class Searcher(Role): async def _act_sp(self) -> Message: """Performs the search action in a single process.""" - logger.info(f"{self._setting}: ready to {self._rc.todo}") + logger.info(f"{self._setting}: to do {self._rc.todo}") response = await self._rc.todo.run(self._rc.memory.get(k=0)) if isinstance(response, (ActionOutput, ActionNode)):