fix bugs and make it perform better

This commit is contained in:
geekan 2023-12-22 17:15:36 +08:00
parent 0d1c0f89cc
commit a7a1195a31
11 changed files with 59 additions and 32 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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