From 485372fa0087f7d7a8529267f3c0c3addd061c26 Mon Sep 17 00:00:00 2001 From: seehi <6580@pm.me> Date: Tue, 16 Jul 2024 21:30:04 +0800 Subject: [PATCH 01/20] update comment --- metagpt/prompts/di/role_zero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 890c1d562..11158e2ce 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -8,7 +8,7 @@ Note: 2. Carefully review your progress at the current task, if your actions so far has not fulfilled the task instruction, you should continue with current task. Otherwise, finish current task by Plan.finish_current_task explicitly. 3. Each time you finish a task, use RoleZero.reply_to_human to report your progress. """ -# To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Available Commands". +# To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Instruction". CMD_PROMPT = """ # Data Structure class Task(BaseModel): From 238acfb9a9d20bf346e1d519b77af58e080bf42f Mon Sep 17 00:00:00 2001 From: seehi <6580@pm.me> Date: Wed, 17 Jul 2024 12:48:00 +0800 Subject: [PATCH 02/20] update comment --- examples/exp_pool/init_exp_pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/exp_pool/init_exp_pool.py b/examples/exp_pool/init_exp_pool.py index 14c415be7..1601abe0b 100644 --- a/examples/exp_pool/init_exp_pool.py +++ b/examples/exp_pool/init_exp_pool.py @@ -85,7 +85,7 @@ def query_exps_count(): async def main(): - await add_exps_from_file("TeamLeader.llm_cached_aask", EXAMPLE_DATA_PATH / "exp_pool/team_leader_exps.json"), + await add_exps_from_file("TeamLeader.llm_cached_aask", EXAMPLE_DATA_PATH / "exp_pool/team_leader_exps.json") await add_exps_from_file("Engineer2.llm_cached_aask", EXAMPLE_DATA_PATH / "exp_pool/engineer_exps.json") query_exps_count() From 5e5dc66cc132a9dfb99a4c8fcff9cfee43f746f3 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Fri, 19 Jul 2024 00:17:33 +0800 Subject: [PATCH 03/20] change routing & add team info for quick question --- metagpt/prompts/di/role_zero.py | 7 +- metagpt/prompts/di/team_leader.py | 7 ++ metagpt/roles/di/role_zero.py | 14 ++-- metagpt/roles/di/team_leader.py | 18 +++- tests/metagpt/roles/di/test_routing.py | 109 +++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 tests/metagpt/roles/di/test_routing.py diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index fd89f9d44..ab1d40106 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -65,7 +65,8 @@ Help check if there are any formatting issues with the JSON data? If so, please QUICK_THINK_PROMPT = """ Decide if the latest user message is a quick question. -Quick questions include common-sense, logical, math questions, greetings, or casual chat that you can answer directly, excluding software development tasks. -Respond with "#YES#, (then start your actual response to the question...)" if so, otherwise, simply respond with "#NO#". -Your response: +Quick questions include common-sense, logical, math, multiple-choice questions, greetings, or casual chat that you can answer directly. +Questions about you or your team info are also quick questions. +Programming or software development tasks are NOT quick questions except for filling a single function or class. +Respond with YES if so, otherwise, NO. Your response: """ diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 484727936..f909524d6 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -24,6 +24,13 @@ Note: 7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement. """ +QUICK_THINK_SYSTEM_PROMPT = """ +{role_info} +Your team member: +{team_info} +However, you MUST respond to the user message by yourself directly, DON'T ask your team members. +""" + FINISH_CURRENT_TASK_CMD = """ ```json [ diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 234064088..a1640768e 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -209,18 +209,20 @@ class RoleZero(Role): return rsp # return output from the last action async def _quick_think(self) -> Message: - msg = self.rc.news[-1] rsp_msg = None - if msg.cause_by != any_to_str(UserRequirement): + if self.rc.news[-1].cause_by != any_to_str(UserRequirement): # Agents themselves won't generate quick questions, use this rule to reduce extra llm calls return rsp_msg - context = self.llm.format_msg(self.get_memories(k=4) + [UserMessage(content=QUICK_THINK_PROMPT)]) + # routing + memory = self.get_memories(k=4) + context = self.llm.format_msg(memory + [UserMessage(content=QUICK_THINK_PROMPT)]) + # print(context) rsp = await self.llm.aask(context) - pattern = r"#YES#,? ?" - if re.search(pattern, rsp): - answer = re.sub(pattern, "", rsp).strip() + if "yes" in rsp.lower(): + # llm call with the original context + answer = await self.llm.aask(self.llm.format_msg(memory)) self.rc.memory.add(AIMessage(content=answer, cause_by=RunCommand)) await self.reply_to_human(content=answer) rsp_msg = AIMessage( diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 2932dd7f0..3781f1a24 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -3,6 +3,7 @@ from __future__ import annotations from metagpt.actions.di.run_command import RunCommand from metagpt.prompts.di.team_leader import ( FINISH_CURRENT_TASK_CMD, + QUICK_THINK_SYSTEM_PROMPT, SYSTEM_PROMPT, TL_INSTRUCTION, ) @@ -16,6 +17,7 @@ from metagpt.tools.tool_registry import register_tool class TeamLeader(RoleZero): name: str = "Tim" profile: str = "Team Leader" + goal: str = "Manage a team to assist users" system_msg: list[str] = [SYSTEM_PROMPT] # TeamLeader only reacts once each time, but may encounter errors or need to ask human, thus allowing 2 more turns @@ -33,16 +35,26 @@ class TeamLeader(RoleZero): } ) - def set_instruction(self): + def _get_team_info(self) -> str: + if not self.rc.env: + return "" team_info = "" for role in self.rc.env.roles.values(): # if role.profile == "Team Leader": # continue team_info += f"{role.name}: {role.profile}, {role.goal}\n" - self.instruction = TL_INSTRUCTION.format(team_info=team_info) + return team_info + + async def _quick_think(self) -> Message: + # insert team info for quick question + self.llm.system_prompt = QUICK_THINK_SYSTEM_PROMPT.format( + role_info=super()._get_prefix(), + team_info=self._get_team_info(), + ) + return await super()._quick_think() async def _think(self) -> bool: - self.set_instruction() + self.instruction = TL_INSTRUCTION.format(team_info=self._get_team_info()) return await super()._think() def publish_message(self, msg: Message, send_to="no one"): diff --git a/tests/metagpt/roles/di/test_routing.py b/tests/metagpt/roles/di/test_routing.py new file mode 100644 index 000000000..ce7b10f67 --- /dev/null +++ b/tests/metagpt/roles/di/test_routing.py @@ -0,0 +1,109 @@ +import asyncio + +from metagpt.environment.mgx.mgx_env import MGXEnv +from metagpt.roles import Architect, ProductManager, ProjectManager +from metagpt.roles.di.data_analyst import DataAnalyst +from metagpt.roles.di.engineer2 import Engineer2 +from metagpt.roles.di.team_leader import TeamLeader +from metagpt.schema import Message + +NORMAL_QUESTION = [ + "create a 2048 game", + "write a snake game", + "Write a 2048 game using JavaScript without using any frameworks, user can play with keyboard.", + "print statistic summary of sklearn iris dataset", + "Run data analysis on sklearn Wine recognition dataset, and train a model to predict wine class (20% as validation), and show validation accuracy.", + """ + Get data from `paperlist` table in https://papercopilot.com/statistics/iclr-statistics/iclr-2024-statistics/, + and save it to a csv file. paper title must include `multiagent` or `large language model`. *notice: print key variables* + """, + """ + Write a fix for this issue: https://github.com/langchain-ai/langchain/issues/20453, + you can fix it on this repo https://github.com/garylin2099/langchain, + checkout a branch named test-fix, commit your changes, push, and create a PR to the master branch of https://github.com/iorisa/langchain + """, +] + +QUICK_QUESTION = [ + # general knowledge qa, logical, math + """Who is the first man landing on Moon""", + """In DNA adenine normally pairs with: A. cytosine. B. guanine. C. thymine. D. uracil. Answer:""", + """________________ occur(s) where there is no prior history of exchange and no future exchanges are expected between a buyer and seller. A. Relationship marketing. B. Service mix. C. Market exchanges. D. Service failure. Answer:""", + """Within American politics, the power to accord official recognition to other countries belongs to A. the Senate. B. the president. C. the Secretary of State. D. the chairman of the Joint Chiefs. Answer:""", + """Find the degree for the given field extension Q(sqrt(2), sqrt(3), sqrt(18)) over Q.""", + """True or false? Statement 1 | A ring homomorphism is one to one if and only if the kernel is {{0}},. Statement 2 | Q is an ideal in R""", + """Jean has 30 lollipops. Jean eats 2 of the lollipops. With the remaining lollipops, Jean wants to package 2 lollipops in one bag. How many bags can Jean fill?""", + """Alisa biked 12 miles per hour for 4.5 hours. Stanley biked at 10 miles per hour for 2.5 hours. How many miles did Alisa and Stanley bike in total?""", + # function filling (humaneval) + """ + def has_close_elements(numbers: List[float], threshold: float) -> bool: + ''' Check if in given list of numbers, are any two numbers closer to each other than + given threshold. + >>> has_close_elements([1.0, 2.0, 3.0], 0.5) + False + >>> has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3) + True + ''' + """, + """ + def is_palindrome(string: str) -> bool: + ''' Test if given string is a palindrome ''' + return string == string[::-1] + + + def make_palindrome(string: str) -> str: + ''' Find the shortest palindrome that begins with a supplied string. + Algorithm idea is simple: + - Find the longest postfix of supplied string that is a palindrome. + - Append to the end of the string reverse of a string prefix that comes before the palindromic suffix. + >>> make_palindrome('') + '' + >>> make_palindrome('cat') + 'catac' + >>> make_palindrome('cata') + 'catac' + ''' + """, + # casual chat + """What's your name?""", + "Who are you", + "What can you do", + "Hi", + "1+1", +] + + +async def test_routing_acc(): + role = TeamLeader() + + env = MGXEnv() + env.add_roles( + [ + role, + ProductManager(), + Architect(), + ProjectManager(), + Engineer2(), + DataAnalyst(), + ] + ) + + for q in QUICK_QUESTION: + msg = Message(content=q) + role.put_message(msg) + await role._observe() + rsp = await role._quick_think() + role.rc.memory.clear() + assert rsp + + for q in NORMAL_QUESTION: + msg = Message(content=q) + role.put_message(msg) + await role._observe() + rsp = await role._quick_think() + role.rc.memory.clear() + assert not rsp + + +if __name__ == "__main__": + asyncio.run(test_routing_acc()) From 900b6af3677c9c207a51fb0b7a6c5af5301a3227 Mon Sep 17 00:00:00 2001 From: seeker Date: Fri, 19 Jul 2024 11:26:57 +0800 Subject: [PATCH 04/20] update: update token_counter, add models: [gpt-4o-mini,gpt-4o-mini-2024-07-18,openrouter/* --- metagpt/utils/token_counter.py | 53 +++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index 1019a17af..83b4826db 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -11,8 +11,13 @@ ref4: https://github.com/hwchase17/langchain/blob/master/langchain/chat_models/o ref5: https://ai.google.dev/models/gemini """ import tiktoken +from openai.types import CompletionUsage +from openai.types.chat import ChatCompletionChunk + +from metagpt.utils.ahttp_client import apost TOKEN_COSTS = { + "anthropic/claude-3.5-sonnet": {"prompt": 0.003, "completion": 0.015}, "gpt-3.5-turbo": {"prompt": 0.0015, "completion": 0.002}, "gpt-3.5-turbo-0301": {"prompt": 0.0015, "completion": 0.002}, "gpt-3.5-turbo-0613": {"prompt": 0.0015, "completion": 0.002}, @@ -31,9 +36,11 @@ TOKEN_COSTS = { "gpt-4-0125-preview": {"prompt": 0.01, "completion": 0.03}, "gpt-4-1106-preview": {"prompt": 0.01, "completion": 0.03}, "gpt-4-vision-preview": {"prompt": 0.01, "completion": 0.03}, # TODO add extra image price calculator + "gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03}, "gpt-4o": {"prompt": 0.005, "completion": 0.015}, "gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015}, - "gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03}, + "gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006}, + "gpt-4o-mini-2024-07-18": {"prompt": 0.00015, "completion": 0.0006}, "text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0}, "glm-3-turbo": {"prompt": 0.0007, "completion": 0.0007}, # 128k version, prompt + completion tokens=0.005¥/k-tokens "glm-4": {"prompt": 0.014, "completion": 0.014}, # 128k version, prompt + completion tokens=0.1¥/k-tokens @@ -53,6 +60,16 @@ TOKEN_COSTS = { "claude-3-opus-20240229": {"prompt": 0.015, "completion": 0.075}, "yi-34b-chat-0205": {"prompt": 0.0003, "completion": 0.0003}, "yi-34b-chat-200k": {"prompt": 0.0017, "completion": 0.0017}, + "openai/gpt-4": {"prompt": 0.03, "completion": 0.06}, # start, for openrouter + "openai/gpt-4-turbo": {"prompt": 0.01, "completion": 0.03}, + "openai/gpt-4o": {"prompt": 0.005, "completion": 0.015}, + "openai/gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015}, + "openai/gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006}, + "openai/gpt-4o-mini-2024-07-18": {"prompt": 0.00015, "completion": 0.0006}, + "google/gemini-pro-1.5": {"prompt": 0.0025, "completion": 0.0075}, + "google/gemini-flash-1.5": {"prompt": 0.00025, "completion": 0.00075}, + "deepseek/deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, + "deepseek/deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, # end, for openrouter } @@ -149,6 +166,8 @@ FIREWORKS_GRADE_TOKEN_COSTS = { TOKEN_MAX = { "gpt-4o-2024-05-13": 128000, "gpt-4o": 128000, + "gpt-4o-mini": 128000, + "gpt-4o-mini-2024-07-18": 128000, "gpt-4-0125-preview": 128000, "gpt-4-turbo-preview": 128000, "gpt-4-1106-preview": 128000, @@ -185,6 +204,17 @@ TOKEN_MAX = { "claude-3-opus-20240229": 200000, "yi-34b-chat-0205": 4000, "yi-34b-chat-200k": 200000, + "openai/gpt-4": 8192, # start, for openrouter + "openai/gpt-4-turbo": 128000, + "openai/gpt-4o": 128000, + "openai/gpt-4o-2024-05-13": 128000, + "openai/gpt-4o-mini": 128000, + "openai/gpt-4o-mini-2024-07-18": 128000, + "anthropic/claude-3.5-sonnet": 200000, + "google/gemini-pro-1.5": 2800000, + "google/gemini-flash-1.5": 2800000, + "deepseek/deepseek-coder": 128000, + "deepseek/deepseek-chat": 128000, # end, for openrouter } @@ -214,6 +244,8 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0125"): "gpt-4-1106-vision-preview", "gpt-4o-2024-05-13", "gpt-4o", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", }: tokens_per_message = 3 # # every reply is primed with <|start|>assistant<|message|> tokens_per_name = 1 @@ -288,3 +320,22 @@ def get_max_completion_tokens(messages: list[dict], model: str, default: int) -> if model not in TOKEN_MAX: return default return TOKEN_MAX[model] - count_message_tokens(messages) - 1 + + +async def get_openrouter_tokens(chunk: ChatCompletionChunk) -> CompletionUsage: + """ + refs to https://openrouter.ai/docs#querying-cost-and-stats + Returns the number of tokens used in a chat completion chunk. + Args: + chunk: The chat completion chunk. + Returns: + The number of tokens used in the chat completion chunk. + """ + url = f"https://openrouter.ai/api/v1/generation?id={chunk.id}" + resp = await apost(url=url, as_json=True) + tokens_prompt = resp.get("tokens_prompt", 0) + completion_tokens = resp.get("tokens_completion", 0) + usage = CompletionUsage( + prompt_tokens=tokens_prompt, completion_tokens=completion_tokens, total_tokens=tokens_prompt + completion_tokens + ) + return usage From c0e93263383d04d6b5a05eeb5348d9f72736a14a Mon Sep 17 00:00:00 2001 From: seeker Date: Fri, 19 Jul 2024 14:29:23 +0800 Subject: [PATCH 05/20] update: update token_counter, add models: deepseek-coder,deepseek-chat --- metagpt/utils/token_counter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index 83b4826db..eb9e7a051 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -70,6 +70,8 @@ TOKEN_COSTS = { "google/gemini-flash-1.5": {"prompt": 0.00025, "completion": 0.00075}, "deepseek/deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, "deepseek/deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, # end, for openrouter + "deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, + "deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, } @@ -215,6 +217,8 @@ TOKEN_MAX = { "google/gemini-flash-1.5": 2800000, "deepseek/deepseek-coder": 128000, "deepseek/deepseek-chat": 128000, # end, for openrouter + "deepseek-chat": 128000, + "deepseek-coder": 128000, } From 59d8d8b1936050b248b96522ea2605da19186981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 23 Jul 2024 17:49:24 +0800 Subject: [PATCH 06/20] =?UTF-8?q?=E4=BF=AE=E6=AD=A3TL=20=E7=AD=89=E5=BE=85?= =?UTF-8?q?=E6=88=90=E5=91=98=E5=AE=8C=E6=88=90=E4=BB=BB=E5=8A=A1=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E7=BB=93=E6=9D=9F=E4=BB=BB=E5=8A=A1=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/design_api.py | 2 +- metagpt/actions/design_api_an.py | 4 ++-- metagpt/actions/project_management_an.py | 3 ++- metagpt/actions/write_prd.py | 2 +- metagpt/prompts/di/engineer2.py | 6 +++++- metagpt/prompts/di/role_zero.py | 13 +++++++++++- metagpt/prompts/di/team_leader.py | 6 +++++- metagpt/roles/architect.py | 2 +- metagpt/roles/di/role_zero.py | 8 ++++---- metagpt/strategy/experience_retriever.py | 25 +++++++++++++++++++++++- metagpt/tools/libs/editor.py | 3 ++- 11 files changed, 59 insertions(+), 15 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 62b0d435d..9bbb3c0a7 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -279,4 +279,4 @@ class WriteDesign(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=design.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'System Design filename: "{str(output_pathname)}"' + return f'System Design filename: "{str(output_pathname)}". \n The "System Design" task has been completed and is now marked as finished.' diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py index 5977cbd95..2541d0469 100644 --- a/metagpt/actions/design_api_an.py +++ b/metagpt/actions/design_api_an.py @@ -33,8 +33,8 @@ PROJECT_NAME = ActionNode( FILE_LIST = ActionNode( key="File list", expected_type=List[str], - instruction="Only need relative paths. ALWAYS write a main.py or app.py here", - example=["main.py", "game.py"], + instruction="Only need relative paths. Succinctly designate the correct entry file for your project based on the programming language: use main.js for JavaScript, main.py for Python, and so on for other languages.", + example=["a.js", "b.py", "c.css", "d.html"], ) REFINED_FILE_LIST = ActionNode( diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py index 0417c0ce4..01b92b7fc 100644 --- a/metagpt/actions/project_management_an.py +++ b/metagpt/actions/project_management_an.py @@ -27,7 +27,8 @@ LOGIC_ANALYSIS = ActionNode( key="Logic Analysis", expected_type=List[List[str]], instruction="Provide a list of files with the classes/methods/functions to be implemented, " - "including dependency analysis and imports.", + "including dependency analysis and imports." + "Ensure consistency between System Design and Logic Analysis; the files must match exactly.", example=[ ["game.py", "Contains Game class and ... functions"], ["main.py", "Contains main function, from game import Game"], diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 42154837a..caa814a81 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -323,4 +323,4 @@ class WritePRD(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=new_prd.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'PRD filename: "{str(output_pathname)}"' + return f'PRD filename: "{str(output_pathname)}". The "Create the PRD" task has been completed and is now marked as finished.' diff --git a/metagpt/prompts/di/engineer2.py b/metagpt/prompts/di/engineer2.py index c4dec8d5e..d81ef5691 100644 --- a/metagpt/prompts/di/engineer2.py +++ b/metagpt/prompts/di/engineer2.py @@ -11,7 +11,11 @@ EXTRA_INSTRUCTION = """ 11. Write out EVERY CODE DETAIL, DON'T LEAVE TODO. 12. To modify code in a file, read the entire file, make changes, and update the file with the complete code, ensuring that no line numbers are included in the final write. 13. When a system design or project schedule is provided, at the end of the plan, add a CodeRview Task for each file; for example, if there are three files, add three CodeRview Tasks. For each CodeRview Task, just call ReviewAndRewriteCode.run. -14. When you are making plan.it is hightly recommand to plan all the coding plan and reviews plan in first response. +14. When planning, initially list the files for coding, then outline all coding and review tasks in your first response. +15. Note 'Task for {file_name} completed.' — signifies the {file_name} coding task is done. +16. Avoid re-reviewing or re-coding the same code. When you decide to take a write or review action, include the command 'finish current task' in the same response. +17. When coding JavaScript, avoid using '\'' in strings. +18. If you plan to read a file, do not include other plans in the same response. """ diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 67c1e84fb..26b06534c 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -12,6 +12,9 @@ Note: """ # To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Available Commands". CMD_PROMPT = """ +# observation +{observation} + # Data Structure class Task(BaseModel): task_id: str = "" @@ -42,10 +45,15 @@ Special Command: Use {{"command_name": "end"}} to do nothing or indicate complet Pay close attention to the Example provided, you can reuse the example for your current situation if it fits. You may use any of the available commands to create a plan or update the plan. You may output mutiple commands, they will be executed sequentially. If you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON'T append a new task. -Pay close attention to what you have done. Be different with your previous action. +Review the latest plan's outcome, focusing on achievements. If your completed task matches the current, consider it finished. +In your response, include at least one command. # Your commands in a json array, in the following output format with correct command_name and args. If there is nothing to do, use the pass or end command: Some text indicating your thoughts before JSON is required, such as what tasks have been completed, what tasks are next, how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them. +Firstly, describe in natural language the actions you have taken recently. +Secondly, describe in natural language the messages you have received recently, with a particular emphasis on messages from users. +Thirdly, describe your current task in natural language. Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. +Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. ```json [ {{ @@ -67,7 +75,10 @@ JSON_REPAIR_PROMPT = """ ```json ``` +Do not use escape characters in json_data, particularly within file paths. Help check if there are any formatting issues with the JSON data? If so, please help format it. +If no issues are detected, the original json data should be returned unchanged. +Output the JSON data in a format that can be loaded by the json.loads() function. """ QUICK_THINK_PROMPT = """ diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index f909524d6..b6611f55d 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -13,7 +13,7 @@ When creating a new plan involving multiple members, create all tasks at once. If plan is created, you should track the progress based on team member feedback message, and update plan accordingly, such as Plan.finish_current_task, Plan.reset_task, Plan.replace_task, etc. You should use TeamLeader.publish_team_message to team members, asking them to start their task. DONT omit any necessary info such as path, link, environment, programming language, framework, requirement, constraint from original content to team members because you are their sole info source. Pay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to the user directly, DON'T ask your team members. - +Pay close attention to messages from team members. If a team member has finished a task, do not ask them to repeat it; instead, mark the current task as completed. Note: 1. If the requirement is a pure DATA-RELATED requirement, such as bug fixes, issue reporting, environment setup, terminal operations, pip install, web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. 2. If the requirement is developing a software, game, app, or website, excluding the above data-related tasks, you should decompose the requirement into multiple tasks and assign them to different team members based on their expertise, usually the sequence of Product Manager -> Architect -> Project Manager -> Engineer -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement. @@ -22,6 +22,10 @@ Note: 5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear. 6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) and remind Engineer to definitely read them when publishing message to Engineer. 7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement. +8. If the receiver message reads 'from {{team member}} to {{\'\'}}, it indicates that someone has completed the current task. For exmaple,'from Alice(Product Manager) to {{\'\'}} means PDR is created,'from Bob(Architecture) to {{\'\'}} means software architecture is created. Note this in your thoughts. +9. Do not use the 'end' command when the current task remains unfinished; instead, use the 'finish_current_task' command to indicate completion before switching to the next task. +10. If you have made a plan, simply follow it without creating a new one. +11. Do not use escape characters in json data, particularly within file paths. """ QUICK_THINK_SYSTEM_PROMPT = """ diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index f640d4a87..48b078dc6 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -31,7 +31,7 @@ class Architect(RoleZero): "libraries. Use same language as user requirement" ) - instruction: str = """Use WriteDesign tool to write a system design document if a system design is required; Use `write_trd_and_framework` tool to write a software framework if a software framework is required;""" + instruction: str = """Do not output the document directly. Use WriteDesign tool to write a system design document if a system design is required; Use `write_trd_and_framework` tool to write a software framework if a software framework is required;""" max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later tools: list[str] = [ "Editor:write,read,write_content", diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 9355ef8ab..04a66f761 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -146,6 +146,8 @@ class RoleZero(Role): tool_info = json.dumps({tool.name: tool.schemas for tool in tools}) ### Make Decision Dynamically ### + + memory = self.rc.memory.get(self.memory_k) instruction = self.instruction.strip() prompt = self.cmd_prompt.format( example=example, @@ -154,10 +156,9 @@ class RoleZero(Role): plan_status=plan_status, current_task=current_task, instruction=instruction, + observation=memory[-1].content, ) - memory = self.rc.memory.get(self.memory_k) memory = await self.parse_browser_actions(memory) - req = self.llm.format_msg(memory + [UserMessage(content=prompt)]) async with ThoughtReporter(enable_llm_stream=True) as reporter: await reporter.async_report({"type": "react"}) @@ -169,7 +170,6 @@ class RoleZero(Role): self.command_rsp = await self.llm_cached_aask(req=req, system_msgs=self.system_msg, state_data=state_data) self.rc.memory.add(AIMessage(content=self.command_rsp)) - return True @exp_cache(context_builder=RoleZeroContextBuilder(), serializer=RoleZeroSerializer()) @@ -340,7 +340,7 @@ class RoleZero(Role): elif cmd["command_name"] == "end": self._set_state(-1) - command_output = "Everything Done" + command_output = " " return command_output diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index 0f805d44c..43a9677f8 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -611,7 +611,7 @@ Explanation: The user is asking for a general update on the project status. Give ] ``` -## example 4 +## example 5 OBSERVATION : current task is none and all task is finished. Explanation: Last task is "Plan.finish_current_task" or 'RoleZero.reply_to_human' and now the current task is none, it means everything is done.Just coutput command "end". ```json @@ -620,6 +620,29 @@ Explanation: Last task is "Plan.finish_current_task" or 'RoleZero.reply_to_human "command_name": "end" } ] + +## example 6 +OBSERVATION : The previously completed task is identical to the current task. +Explanation: The current task has been accomplished previously. +```json +[ + { + "command_name": "Plan.finish_current_task", + "args": {} + }, +] +``` + +## example 7 +OBSERVATION : the task assigned to Alice is still ongoing as it has not been marked as finished. The current task in the plan is for Alice to create the PRD. +Explanation: "I attempted to locate historical records containing 'send to []', and discovered an entry stating 'PRD is finished and masked.' This indicates that Alice's task has been completed. +```json +[ + { + "command_name": "Plan.finish_current_task", + "args": {} + }, +] ``` """ diff --git a/metagpt/tools/libs/editor.py b/metagpt/tools/libs/editor.py index 24eca65fc..044618d79 100644 --- a/metagpt/tools/libs/editor.py +++ b/metagpt/tools/libs/editor.py @@ -36,9 +36,10 @@ class Editor: with open(path, "w", encoding="utf-8") as f: f.write(content) # self.resource.report(path, "path") + return f" The task of writing/coding the '{os.path.basename(path)}' file has been completed. The file '{os.path.basename(path)}' has been successfully created." def read(self, path: str) -> FileBlock: - """Read the whole content of a file. It is strongly advised to utilize absolute paths""" + """Read the whole content of a file. Using absolute paths as the argument for specifying the file location.""" with open(path, "r") as f: self.resource.report(path, "path") lines = f.readlines() From f4204b4beb43a9686d114058aff61a71b7f7fcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 23 Jul 2024 20:07:00 +0800 Subject: [PATCH 07/20] =?UTF-8?q?WriteCodeReview=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4review=E5=AE=8C=E6=88=90=E7=9A=84=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/write_code_review.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py index e72fe5cd1..f49ef178f 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -283,7 +283,10 @@ class ReviewAndRewriteCode(Action): await awrite(filename=code_path, data=code) - return code + return ( + f'\nThe "review and rewrite the code {os.path.basename(code_path)}" task has been completed and is now marked as finished.\n' + + code + ) @staticmethod async def _try_aread(input: str) -> str: From 0ec3ac813683afbe4b766bd883a12925f8277704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Wed, 24 Jul 2024 09:59:31 +0800 Subject: [PATCH 08/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=88=A0=E9=99=A4=E5=86=97=E4=BD=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/design_api.py | 2 +- metagpt/prompts/di/role_zero.py | 12 ++++++------ metagpt/prompts/di/team_leader.py | 2 +- metagpt/roles/architect.py | 2 +- metagpt/roles/di/role_zero.py | 3 +-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 9bbb3c0a7..322108dbb 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -279,4 +279,4 @@ class WriteDesign(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=design.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'System Design filename: "{str(output_pathname)}". \n The "System Design" task has been completed and is now marked as finished.' + return f'System Design filename: "{str(output_pathname)}". \n The System Design has been completed and is now marked as finished.' diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 26b06534c..e91992f35 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -12,8 +12,8 @@ Note: """ # To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Available Commands". CMD_PROMPT = """ -# observation -{observation} +# latest observation +{latest_observation} # Data Structure class Task(BaseModel): @@ -50,9 +50,9 @@ In your response, include at least one command. # Your commands in a json array, in the following output format with correct command_name and args. If there is nothing to do, use the pass or end command: Some text indicating your thoughts before JSON is required, such as what tasks have been completed, what tasks are next, how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them. -Firstly, describe in natural language the actions you have taken recently. -Secondly, describe in natural language the messages you have received recently, with a particular emphasis on messages from users. -Thirdly, describe your current task in natural language. Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. +Firstly, describe the actions you have taken recently. +Secondly, describe the messages you have received recently, with a particular emphasis on messages from users. +Thirdly, describe your current task . Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. ```json [ @@ -75,7 +75,7 @@ JSON_REPAIR_PROMPT = """ ```json ``` -Do not use escape characters in json_data, particularly within file paths. +Do not use escape characters in json data, particularly within file paths. Help check if there are any formatting issues with the JSON data? If so, please help format it. If no issues are detected, the original json data should be returned unchanged. Output the JSON data in a format that can be loaded by the json.loads() function. diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index b6611f55d..11445abf5 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -22,7 +22,7 @@ Note: 5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear. 6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) and remind Engineer to definitely read them when publishing message to Engineer. 7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement. -8. If the receiver message reads 'from {{team member}} to {{\'\'}}, it indicates that someone has completed the current task. For exmaple,'from Alice(Product Manager) to {{\'\'}} means PDR is created,'from Bob(Architecture) to {{\'\'}} means software architecture is created. Note this in your thoughts. +8. If the receiver message reads 'from {{team member}} to {{\'\'}}, it indicates that someone has completed the current task. Note this in your thoughts. 9. Do not use the 'end' command when the current task remains unfinished; instead, use the 'finish_current_task' command to indicate completion before switching to the next task. 10. If you have made a plan, simply follow it without creating a new one. 11. Do not use escape characters in json data, particularly within file paths. diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index 48b078dc6..f640d4a87 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -31,7 +31,7 @@ class Architect(RoleZero): "libraries. Use same language as user requirement" ) - instruction: str = """Do not output the document directly. Use WriteDesign tool to write a system design document if a system design is required; Use `write_trd_and_framework` tool to write a software framework if a software framework is required;""" + instruction: str = """Use WriteDesign tool to write a system design document if a system design is required; Use `write_trd_and_framework` tool to write a software framework if a software framework is required;""" max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later tools: list[str] = [ "Editor:write,read,write_content", diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 04a66f761..f9591d693 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -146,7 +146,6 @@ class RoleZero(Role): tool_info = json.dumps({tool.name: tool.schemas for tool in tools}) ### Make Decision Dynamically ### - memory = self.rc.memory.get(self.memory_k) instruction = self.instruction.strip() prompt = self.cmd_prompt.format( @@ -156,7 +155,7 @@ class RoleZero(Role): plan_status=plan_status, current_task=current_task, instruction=instruction, - observation=memory[-1].content, + latest_observation=memory[-1].content, ) memory = await self.parse_browser_actions(memory) req = self.llm.format_msg(memory + [UserMessage(content=prompt)]) From ca1a44c3c4829bb2b911363ec26d71f2c31c3f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Wed, 24 Jul 2024 10:56:20 +0800 Subject: [PATCH 09/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=E7=9A=84=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/write_prd.py | 2 +- metagpt/prompts/di/role_zero.py | 4 ++-- metagpt/roles/di/role_zero.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index caa814a81..ae1b5dd81 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -323,4 +323,4 @@ class WritePRD(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=new_prd.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'PRD filename: "{str(output_pathname)}". The "Create the PRD" task has been completed and is now marked as finished.' + return f'PRD filename: "{str(output_pathname)}". The product requirement document (PRD) has been completed.' diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index e91992f35..0e2126880 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -12,7 +12,7 @@ Note: """ # To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Available Commands". CMD_PROMPT = """ -# latest observation +# Latest Observation {latest_observation} # Data Structure @@ -50,7 +50,7 @@ In your response, include at least one command. # Your commands in a json array, in the following output format with correct command_name and args. If there is nothing to do, use the pass or end command: Some text indicating your thoughts before JSON is required, such as what tasks have been completed, what tasks are next, how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them. -Firstly, describe the actions you have taken recently. +Firstly, describe the actions you have taken recently. Secondly, describe the messages you have received recently, with a particular emphasis on messages from users. Thirdly, describe your current task . Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index f9591d693..40c03ccad 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -339,7 +339,7 @@ class RoleZero(Role): elif cmd["command_name"] == "end": self._set_state(-1) - command_output = " " + command_output = "" return command_output From 48b8a941b955b149d56183b4a737c602f454772a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Wed, 24 Jul 2024 11:23:52 +0800 Subject: [PATCH 10/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E5=92=8C=E4=BB=BB=E5=8A=A1=E8=80=A6=E5=90=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/design_api.py | 2 +- metagpt/actions/write_code_review.py | 2 +- metagpt/prompts/di/role_zero.py | 1 + metagpt/tools/libs/editor.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 322108dbb..3eb8fe04d 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -279,4 +279,4 @@ class WriteDesign(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=design.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'System Design filename: "{str(output_pathname)}". \n The System Design has been completed and is now marked as finished.' + return f'System Design filename: "{str(output_pathname)}". \n The System Design has been completed.' diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py index f49ef178f..ca174f2ee 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -284,7 +284,7 @@ class ReviewAndRewriteCode(Action): await awrite(filename=code_path, data=code) return ( - f'\nThe "review and rewrite the code {os.path.basename(code_path)}" task has been completed and is now marked as finished.\n' + f"The review and rewriting of the code in the file '{os.path.basename(code_path)}' has been completed." + code ) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 0e2126880..4e75efcfe 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -50,6 +50,7 @@ In your response, include at least one command. # Your commands in a json array, in the following output format with correct command_name and args. If there is nothing to do, use the pass or end command: Some text indicating your thoughts before JSON is required, such as what tasks have been completed, what tasks are next, how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them. +Output should adhere to the following format. Firstly, describe the actions you have taken recently. Secondly, describe the messages you have received recently, with a particular emphasis on messages from users. Thirdly, describe your current task . Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. diff --git a/metagpt/tools/libs/editor.py b/metagpt/tools/libs/editor.py index 044618d79..4c6d2b1dc 100644 --- a/metagpt/tools/libs/editor.py +++ b/metagpt/tools/libs/editor.py @@ -36,7 +36,7 @@ class Editor: with open(path, "w", encoding="utf-8") as f: f.write(content) # self.resource.report(path, "path") - return f" The task of writing/coding the '{os.path.basename(path)}' file has been completed. The file '{os.path.basename(path)}' has been successfully created." + return f"The writing/coding the of the file {os.path.basename(path)}' is now completed. The file '{os.path.basename(path)}' has been successfully created." def read(self, path: str) -> FileBlock: """Read the whole content of a file. Using absolute paths as the argument for specifying the file location.""" From cf345ceea05cee4ec71ae3724dd5e22b1180bcc7 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Wed, 24 Jul 2024 21:55:25 +0800 Subject: [PATCH 11/20] handle programming-related, time-sensitive request --- metagpt/prompts/di/role_zero.py | 10 ++++++---- tests/metagpt/roles/di/test_routing.py | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index dcdb4f5ba..d3f978c15 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -53,7 +53,7 @@ Some text indicating your thoughts before JSON is required, such as what tasks h Output should adhere to the following format. Firstly, describe the actions you have taken recently. Secondly, describe the messages you have received recently, with a particular emphasis on messages from users. -Thirdly, describe your current task . Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. +Thirdly, describe your current task . Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. If all tasks are finished and current task is empty, use the end command to terminate. Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. ```json [ @@ -83,9 +83,11 @@ Output the JSON data in a format that can be loaded by the json.loads() function """ QUICK_THINK_PROMPT = """ -Decide if the latest user message is a quick question. +Decide if the latest user message previously is a quick question. Quick questions include common-sense, logical, math, multiple-choice questions, greetings, or casual chat that you can answer directly. Questions about you or your team info are also quick questions. -Programming or software development tasks are NOT quick questions except for filling a single function or class. -Respond with YES if so, otherwise, NO. Your response: +Time- or location-sensitive questions such as wheather or news inquiry are NOT quick questions. +Software development tasks are NOT quick questions. +However, these programming-related tasks are quick questions: writing trivial code snippets (fewer than 30 lines), filling a single function or class, explaining concepts, writing tutorials and documentation. +Respond with a concise thought then a YES if the question is a quick question, otherwise, a NO. Your response: """ diff --git a/tests/metagpt/roles/di/test_routing.py b/tests/metagpt/roles/di/test_routing.py index ce7b10f67..c476fe9ad 100644 --- a/tests/metagpt/roles/di/test_routing.py +++ b/tests/metagpt/roles/di/test_routing.py @@ -1,6 +1,7 @@ import asyncio from metagpt.environment.mgx.mgx_env import MGXEnv +from metagpt.logs import logger from metagpt.roles import Architect, ProductManager, ProjectManager from metagpt.roles.di.data_analyst import DataAnalyst from metagpt.roles.di.engineer2 import Engineer2 @@ -22,10 +23,16 @@ NORMAL_QUESTION = [ you can fix it on this repo https://github.com/garylin2099/langchain, checkout a branch named test-fix, commit your changes, push, and create a PR to the master branch of https://github.com/iorisa/langchain """, + ## info searching ## + """When is the Olympic football final this year, where will it be held, and where can I buy tickets? If possible, please provide me with a link to buy tickets""", + """Help me search for Inter Miami CF home games in the next 2 months and give me the link to buy tickets""", + """请为我查找位于深圳大学附近1000米范围内,价格适中(性价比最高),且晚上关门时间晚于22:00的健身房。""", + "今天的天气怎样", + "奥运会的开幕式是什么时候", ] QUICK_QUESTION = [ - # general knowledge qa, logical, math + ## general knowledge qa, logical, math ## """Who is the first man landing on Moon""", """In DNA adenine normally pairs with: A. cytosine. B. guanine. C. thymine. D. uracil. Answer:""", """________________ occur(s) where there is no prior history of exchange and no future exchanges are expected between a buyer and seller. A. Relationship marketing. B. Service mix. C. Market exchanges. D. Service failure. Answer:""", @@ -34,7 +41,7 @@ QUICK_QUESTION = [ """True or false? Statement 1 | A ring homomorphism is one to one if and only if the kernel is {{0}},. Statement 2 | Q is an ideal in R""", """Jean has 30 lollipops. Jean eats 2 of the lollipops. With the remaining lollipops, Jean wants to package 2 lollipops in one bag. How many bags can Jean fill?""", """Alisa biked 12 miles per hour for 4.5 hours. Stanley biked at 10 miles per hour for 2.5 hours. How many miles did Alisa and Stanley bike in total?""", - # function filling (humaneval) + ## function filling (humaneval) ## """ def has_close_elements(numbers: List[float], threshold: float) -> bool: ''' Check if in given list of numbers, are any two numbers closer to each other than @@ -70,6 +77,10 @@ QUICK_QUESTION = [ "What can you do", "Hi", "1+1", + # programming-related but not requiring software development SOP + "请写一个python入门教程", + "python里的装饰器是怎么用的,给我个例子", + "写一个java的hello world程序", ] @@ -91,10 +102,13 @@ async def test_routing_acc(): for q in QUICK_QUESTION: msg = Message(content=q) role.put_message(msg) + # await env.run() await role._observe() rsp = await role._quick_think() role.rc.memory.clear() - assert rsp + if not rsp: + logger.error(f"Quick question failed: {q}") + # assert rsp for q in NORMAL_QUESTION: msg = Message(content=q) @@ -102,7 +116,9 @@ async def test_routing_acc(): await role._observe() rsp = await role._quick_think() role.rc.memory.clear() - assert not rsp + # assert not rsp + if rsp: + logger.error(f"Normal question failed: {q}") if __name__ == "__main__": From e5a5223765b7634edf67bf572f9c8f71d300925d Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Wed, 24 Jul 2024 22:14:21 +0800 Subject: [PATCH 12/20] add deepseek token price --- metagpt/utils/token_counter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index 1019a17af..f6ee0a232 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -53,6 +53,8 @@ TOKEN_COSTS = { "claude-3-opus-20240229": {"prompt": 0.015, "completion": 0.075}, "yi-34b-chat-0205": {"prompt": 0.0003, "completion": 0.0003}, "yi-34b-chat-200k": {"prompt": 0.0017, "completion": 0.0017}, + "deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, + "deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, } From da551f052696669fbc5357f6ca128fd50705267d Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Wed, 24 Jul 2024 23:35:36 +0800 Subject: [PATCH 13/20] rm git-push tool --- metagpt/prompts/di/swe_agent.py | 9 ++---- metagpt/roles/di/swe_agent.py | 4 +-- metagpt/tools/libs/git.py | 53 +-------------------------------- 3 files changed, 5 insertions(+), 61 deletions(-) diff --git a/metagpt/prompts/di/swe_agent.py b/metagpt/prompts/di/swe_agent.py index fd95dc997..7f4fb956d 100644 --- a/metagpt/prompts/di/swe_agent.py +++ b/metagpt/prompts/di/swe_agent.py @@ -124,12 +124,9 @@ Thought: The bug has been fixed. Let's submit the changes. ##### Push the changes from the local repository to the remote repository. Thought: All changes have been saved, let's push the code to the remote repository. {{ - "command_name": "git_push", + "command_name": "Bash.run", "args": {{ - "local_path": "/workspace/MetaGPT", - "app_name": "github", - "comments": "Fix Issue #1275: produced TypeError: openai.types.completion_usage.CompletionUsage() argument after ** must be a mapping, not NoneType"", - "new_branch": "test-fix" + "cmd": "git push orign test-fix" }} }} -> @@ -220,7 +217,7 @@ IMPORTANT_TIPS = """ - Based on feedback of observation or bash command in trajectory to guide adjustments in your search strategy. 13. Save the code change: - - If you need to submit changes to the remote repository, first use the regular git commit command to save the changes locally, then select a command from the `Available Commands: [git_push, git_create_pull]` to submit the changes to the remote repository. + - If you need to submit changes to the remote repository, first use the regular git commit command to save the changes locally, then select a command from the `Available Commands: [git_create_pull]` to submit the changes to the remote repository. - If you don't need to submit code changes to the remote repository. use the command Bash.run('submit') to commit the changes locally. diff --git a/metagpt/roles/di/swe_agent.py b/metagpt/roles/di/swe_agent.py index e3cca3330..7be794265 100644 --- a/metagpt/roles/di/swe_agent.py +++ b/metagpt/roles/di/swe_agent.py @@ -9,7 +9,7 @@ from metagpt.prompts.di.swe_agent import ( SWE_AGENT_SYSTEM_TEMPLATE, ) from metagpt.roles.di.role_zero import RoleZero -from metagpt.tools.libs.git import git_create_pull, git_push +from metagpt.tools.libs.git import git_create_pull from metagpt.tools.libs.terminal import Bash @@ -23,7 +23,6 @@ class SWEAgent(RoleZero): "Bash", "Browser:goto,scroll", "RoleZero", - "git_push", "git_create_pull", ] terminal: Bash = Field(default_factory=Bash, exclude=True) @@ -42,7 +41,6 @@ class SWEAgent(RoleZero): self.tool_execution_map.update( { "Bash.run": self.terminal.run, - "git_push": git_push, "git_create_pull": git_create_pull, } ) diff --git a/metagpt/tools/libs/git.py b/metagpt/tools/libs/git.py index a2fd87a92..9a33ee4c1 100644 --- a/metagpt/tools/libs/git.py +++ b/metagpt/tools/libs/git.py @@ -2,8 +2,7 @@ # -*- coding: utf-8 -*- from __future__ import annotations -from pathlib import Path -from typing import Optional, Union +from typing import Optional from github.Issue import Issue from github.PullRequest import PullRequest @@ -11,56 +10,6 @@ from github.PullRequest import PullRequest from metagpt.tools.tool_registry import register_tool -@register_tool(tags=["software development", "git", "Push to remote git repository."]) -async def git_push( - local_path: Union[str, Path], - app_name: str, - comments: str = "Commit", - new_branch: str = "", -) -> "GitBranch": - """ - Pushes changes from a local Git repository to its remote counterpart. - - Args: - local_path (Union[str, Path]): The absolute path to the local Git repository. - app_name (str): The name of the platform hosting the repository (e.g., "github", "gitlab", "bitbucket"). - comments (str, optional): Comments to be associated with the push. Defaults to "Commit". - new_branch (str, optional): The name of the new branch to create and push changes to. - If not provided, changes will be pushed to the current branch. Defaults to "". - - Returns: - GitBranch: The branch to which the changes were pushed. - - Raises: - ValueError: If the provided local_path does not point to a valid Git repository. - - Example: - >>> url = "https://github.com/iorisa/snake-game.git" - >>> local_path = await git_clone(url=url) - >>> app_name = "github" - >>> comments = "Commit" - >>> new_branch = "feature/new" - >>> branch = await git_push(local_path=local_path, app_name=app_name, comments=comments, new_branch=new_branch) - >>> base = branch.base - >>> head = branch.head - >>> repo_name = branch.repo_name - >>> print(f"base branch:'{base}', head branch:'{head}', repo_name:'{repo_name}'") - base branch:'master', head branch:'feature/new', repo_name:'iorisa/snake-game' - """ - - from metagpt.tools.libs import get_env - from metagpt.utils.git_repository import GitRepository - - if not GitRepository.is_git_dir(local_path): - raise ValueError("Invalid local git repository") - - repo = GitRepository(local_path=local_path, auto_init=False) - # Read access token from environment variables. - access_token = await get_env(key="access_token", app_name=app_name) - branch = await repo.push(new_branch=new_branch, comments=comments, access_token=access_token) - return branch - - @register_tool(tags=["software development", "git", "create a git pull request or merge request"]) async def git_create_pull( base: str, From 739bfe22bbfdba8cf179a1ae9a1999f786a130ee Mon Sep 17 00:00:00 2001 From: seeker Date: Thu, 25 Jul 2024 11:12:53 +0800 Subject: [PATCH 14/20] update: openrouter usage --- metagpt/provider/openai_api.py | 3 +++ metagpt/utils/token_counter.py | 23 ----------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index a41c8b0a6..47d7df6f1 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -105,6 +105,9 @@ class OpenAILLM(BaseLLM): elif hasattr(chunk.choices[0], "usage"): # The usage of some services is an attribute of chunk.choices[0], such as Moonshot usage = CompletionUsage(**chunk.choices[0].usage) + if "openrouter.ai" in self.config.base_url and hasattr(chunk, "usage") and chunk.usage is not None: + # due to it get token cost from api + usage = chunk.usage log_llm_stream("\n") full_reply_content = "".join(collected_messages) diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index eb9e7a051..5be6b5f61 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -11,10 +11,6 @@ ref4: https://github.com/hwchase17/langchain/blob/master/langchain/chat_models/o ref5: https://ai.google.dev/models/gemini """ import tiktoken -from openai.types import CompletionUsage -from openai.types.chat import ChatCompletionChunk - -from metagpt.utils.ahttp_client import apost TOKEN_COSTS = { "anthropic/claude-3.5-sonnet": {"prompt": 0.003, "completion": 0.015}, @@ -324,22 +320,3 @@ def get_max_completion_tokens(messages: list[dict], model: str, default: int) -> if model not in TOKEN_MAX: return default return TOKEN_MAX[model] - count_message_tokens(messages) - 1 - - -async def get_openrouter_tokens(chunk: ChatCompletionChunk) -> CompletionUsage: - """ - refs to https://openrouter.ai/docs#querying-cost-and-stats - Returns the number of tokens used in a chat completion chunk. - Args: - chunk: The chat completion chunk. - Returns: - The number of tokens used in the chat completion chunk. - """ - url = f"https://openrouter.ai/api/v1/generation?id={chunk.id}" - resp = await apost(url=url, as_json=True) - tokens_prompt = resp.get("tokens_prompt", 0) - completion_tokens = resp.get("tokens_completion", 0) - usage = CompletionUsage( - prompt_tokens=tokens_prompt, completion_tokens=completion_tokens, total_tokens=tokens_prompt + completion_tokens - ) - return usage From bb7508908b8b47e9d0c37951c04702e1fdefa238 Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Thu, 25 Jul 2024 11:54:12 +0800 Subject: [PATCH 15/20] Revert "add deepseek token price" This reverts commit e5a5223765b7634edf67bf572f9c8f71d300925d. --- metagpt/utils/token_counter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index f6ee0a232..1019a17af 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -53,8 +53,6 @@ TOKEN_COSTS = { "claude-3-opus-20240229": {"prompt": 0.015, "completion": 0.075}, "yi-34b-chat-0205": {"prompt": 0.0003, "completion": 0.0003}, "yi-34b-chat-200k": {"prompt": 0.0017, "completion": 0.0017}, - "deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, - "deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, } From 65674bf49934efcb45468c45643c16c19493269d Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Thu, 25 Jul 2024 11:56:42 +0800 Subject: [PATCH 16/20] update prompt --- metagpt/prompts/di/swe_agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metagpt/prompts/di/swe_agent.py b/metagpt/prompts/di/swe_agent.py index 7f4fb956d..7455cf30a 100644 --- a/metagpt/prompts/di/swe_agent.py +++ b/metagpt/prompts/di/swe_agent.py @@ -126,7 +126,7 @@ Thought: All changes have been saved, let's push the code to the remote reposito {{ "command_name": "Bash.run", "args": {{ - "cmd": "git push orign test-fix" + "cmd": "git push origin test-fix" }} }} -> @@ -217,7 +217,7 @@ IMPORTANT_TIPS = """ - Based on feedback of observation or bash command in trajectory to guide adjustments in your search strategy. 13. Save the code change: - - If you need to submit changes to the remote repository, first use the regular git commit command to save the changes locally, then select a command from the `Available Commands: [git_create_pull]` to submit the changes to the remote repository. + - If you need to submit changes to the remote repository, first use the regular git commit command to save the changes locally, then use git push for pushing, and if requested, `git_create_pull` in Available Commands for creating pull request. - If you don't need to submit code changes to the remote repository. use the command Bash.run('submit') to commit the changes locally. From 8483d5ae1ee02b96a67dd986211757f311c5c44a Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Fri, 26 Jul 2024 09:58:38 +0800 Subject: [PATCH 17/20] Enhance exception chaining for better error traceability in role_raise_decorator --- metagpt/utils/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index e8f150556..eea16bb2e 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -660,7 +660,7 @@ def role_raise_decorator(func): if re.match(r"^openai\.", name) or re.match(r"^httpx\.", name): raise last_error - raise Exception(format_trackback_info(limit=None)) + raise Exception(format_trackback_info(limit=None)) from e return wrapper From d13342f4a522d9cd8a87c2a4a94bd6e30bd69d64 Mon Sep 17 00:00:00 2001 From: shenchucheng Date: Fri, 26 Jul 2024 09:59:46 +0800 Subject: [PATCH 18/20] add Deepseek/Siliconflow LLMType --- metagpt/configs/llm_config.py | 2 ++ metagpt/provider/openai_api.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/metagpt/configs/llm_config.py b/metagpt/configs/llm_config.py index 39f6e61f1..48130eedc 100644 --- a/metagpt/configs/llm_config.py +++ b/metagpt/configs/llm_config.py @@ -32,6 +32,8 @@ class LLMType(Enum): MISTRAL = "mistral" YI = "yi" # lingyiwanwu OPEN_ROUTER = "open_router" + DEEPSEEK = "deepseek" + SILICONFLOW = "siliconflow" def __missing__(self, key): return self.OPENAI diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 47d7df6f1..fa689d54f 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -49,6 +49,8 @@ from metagpt.utils.token_counter import ( LLMType.MISTRAL, LLMType.YI, LLMType.OPEN_ROUTER, + LLMType.DEEPSEEK, + LLMType.SILICONFLOW, ] ) class OpenAILLM(BaseLLM): From 1aff69a72cb20bef22fe73958fc33e86f5f76d59 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Fri, 26 Jul 2024 10:21:40 +0800 Subject: [PATCH 19/20] change role responsibility --- metagpt/prompts/di/team_leader.py | 2 +- metagpt/roles/di/data_analyst.py | 36 ++++++++++++++++++------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 11445abf5..acd44d56c 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -15,7 +15,7 @@ You should use TeamLeader.publish_team_message to team members, asking them to s Pay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to the user directly, DON'T ask your team members. Pay close attention to messages from team members. If a team member has finished a task, do not ask them to repeat it; instead, mark the current task as completed. Note: -1. If the requirement is a pure DATA-RELATED requirement, such as bug fixes, issue reporting, environment setup, terminal operations, pip install, web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. +1. If the requirement is a pure DATA-RELATED requirement, such as web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. 2. If the requirement is developing a software, game, app, or website, excluding the above data-related tasks, you should decompose the requirement into multiple tasks and assign them to different team members based on their expertise, usually the sequence of Product Manager -> Architect -> Project Manager -> Engineer -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement. 3. If the requirement contains both DATA-RELATED part mentioned in 1 and software development part mentioned in 2, you should decompose the software development part and assign them to different team members based on their expertise, and assign the DATA-RELATED part to Data Analyst David directly. 4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members. diff --git a/metagpt/roles/di/data_analyst.py b/metagpt/roles/di/data_analyst.py index 1debd681c..abb501830 100644 --- a/metagpt/roles/di/data_analyst.py +++ b/metagpt/roles/di/data_analyst.py @@ -8,10 +8,15 @@ from pydantic import Field, model_validator from metagpt.actions.di.execute_nb_code import ExecuteNbCode from metagpt.actions.di.write_analysis_code import WriteAnalysisCode from metagpt.logs import logger -from metagpt.prompts.di.data_analyst import EXTRA_INSTRUCTION, TASK_TYPE_DESC, CODE_STATUS, BROWSER_INFO +from metagpt.prompts.di.data_analyst import ( + BROWSER_INFO, + CODE_STATUS, + EXTRA_INSTRUCTION, + TASK_TYPE_DESC, +) from metagpt.prompts.di.role_zero import ROLE_INSTRUCTION from metagpt.roles.di.role_zero import RoleZero -from metagpt.schema import TaskResult, Message +from metagpt.schema import Message, TaskResult from metagpt.strategy.experience_retriever import ExpRetriever, KeywordExpRetriever from metagpt.tools.tool_recommend import BM25ToolRecommender, ToolRecommender from metagpt.tools.tool_registry import register_tool @@ -21,7 +26,7 @@ from metagpt.tools.tool_registry import register_tool class DataAnalyst(RoleZero): name: str = "David" profile: str = "DataAnalyst" - goal: str = "Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web searching, web deployment, terminal operation, git and github operation, etc." + goal: str = "Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web searching, web deployment, terminal operation, etc." instruction: str = ROLE_INSTRUCTION + EXTRA_INSTRUCTION task_type_desc: str = TASK_TYPE_DESC @@ -40,21 +45,22 @@ class DataAnalyst(RoleZero): self.custom_tool_recommender = BM25ToolRecommender(tools=self.custom_tools) def _update_tool_execution(self): - self.tool_execution_map.update({ - "DataAnalyst.write_and_exec_code": self.write_and_exec_code, - }) + self.tool_execution_map.update( + { + "DataAnalyst.write_and_exec_code": self.write_and_exec_code, + } + ) async def parse_browser_actions(self, memory: List[Message]) -> List[Message]: memory = await super().parse_browser_actions(memory) browser_actions = [] for index, msg in enumerate(memory): if msg.cause_by == "browser": - browser_url = re.search('URL: (.*?)\\n', msg.content).group(1) + browser_url = re.search("URL: (.*?)\\n", msg.content).group(1) pattern = re.compile(r"Command Browser\.(\w+) executed") - browser_actions.append({ - 'command': pattern.match(memory[index - 1].content).group(1), - 'current url': browser_url - }) + browser_actions.append( + {"command": pattern.match(memory[index - 1].content).group(1), "current url": browser_url} + ) if browser_actions: browser_actions = BROWSER_INFO.format(browser_actions=browser_actions) self.rc.working_memory.add(Message(content=browser_actions, role="user", cause_by="browser")) @@ -84,8 +90,8 @@ class DataAnalyst(RoleZero): while not success and counter < 3: ### write code ### - logger.info(f"ready to WriteAnalysisCode") - use_reflection = (counter > 0 and self.use_reflection) # only use reflection after the first trial + logger.info("ready to WriteAnalysisCode") + use_reflection = counter > 0 and self.use_reflection # only use reflection after the first trial code = await self.write_code.run( user_requirement=self.planner.plan.goal, @@ -108,9 +114,9 @@ class DataAnalyst(RoleZero): task_result = TaskResult(code=code, result=result, is_success=success) self.planner.current_task.update_task_result(task_result) - status = 'Success' if success else 'Failed' + status = "Success" if success else "Failed" output = CODE_STATUS.format(code=code, status=status, result=result) if success: - output += 'The code written has been executed successfully.' + output += "The code written has been executed successfully." self.rc.working_memory.clear() return output From 065efb9f4bbc4a35515486283fc88466f3e5ebf8 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Fri, 26 Jul 2024 10:23:36 +0800 Subject: [PATCH 20/20] rm Editor tool for PM to understand better --- metagpt/roles/product_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index 5f70bf390..7933e74b8 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -33,9 +33,9 @@ class ProductManager(RoleZero): constraints: str = "utilize the same language as the user requirements for seamless communication" todo_action: str = any_to_name(WritePRD) - instruction: str = """Use WritePRD tool to write PRD if a PRD is required; Use `Pic2Txt` tool to write out an intact textual user requirements if an intact textual user requiremnt is required given some images alongside the contextual textual descriptions;""" + instruction: str = """Use WritePRD tool to write PRD if a PRD is required, users may asks for a software without mentioning PRD, but you should output the PRD of that software; Use `Pic2Txt` tool to write out an intact textual user requirements if an intact textual user requiremnt is required given some images alongside the contextual textual descriptions""" max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later - tools: list[str] = ["Editor:write,read,write_content", "RoleZero", "WritePRD", Pic2Txt.__name__] + tools: list[str] = ["RoleZero", "WritePRD", Pic2Txt.__name__] def __init__(self, **kwargs) -> None: super().__init__(**kwargs)