From 99c440b17a65e0e76bf893c0d14be24e7f36117a Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Sun, 4 Aug 2024 12:40:41 +0800 Subject: [PATCH 1/6] add search intent & fix a bug in parsing browser action --- metagpt/actions/search_enhanced_qa.py | 7 +++- metagpt/prompts/di/role_zero.py | 4 +- metagpt/roles/di/role_zero.py | 23 +++++++---- tests/metagpt/roles/di/test_routing.py | 55 ++++++++++++++++++++------ 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/metagpt/actions/search_enhanced_qa.py b/metagpt/actions/search_enhanced_qa.py index e3340c17b..95612aa7b 100644 --- a/metagpt/actions/search_enhanced_qa.py +++ b/metagpt/actions/search_enhanced_qa.py @@ -11,6 +11,7 @@ from metagpt.actions.research import CollectLinks, WebBrowseAndSummarize from metagpt.logs import logger from metagpt.tools.web_browser_engine import WebBrowserEngine from metagpt.utils.common import CodeParser +from metagpt.utils.report import ThoughtReporter REWRITE_QUERY_PROMPT = """ Role: You are a highly efficient assistant that provide a better search query for web search engine to answer the given question. @@ -237,4 +238,8 @@ class SearchEnhancedQA(Action): system_prompt = SEARCH_ENHANCED_QA_SYSTEM_PROMPT.format(context=context) - return await self._aask(query, [system_prompt]) + async with ThoughtReporter(enable_llm_stream=True) as reporter: + await reporter.async_report({"type": "quick"}) + rsp = await self._aask(query, [system_prompt]) + + return rsp diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 5891522bd..721fe7e60 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -114,8 +114,8 @@ QUICK_THINK_PROMPT = """ Decide if the latest user message previously is a quick question. Quick questions include common-sense, legal, 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. -Time- or location-sensitive questions such as wheather or news inquiry are NOT quick questions. +Time- or location-sensitive questions such as wheather or news inquiry are NOT quick questions. Moreover, you should output a keyword SEARCH to indicate the need for a google search. 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: +Respond with a concise thought then a YES if the question is a quick question, otherwise, a NO or a SEARCH. Your response: """ diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index af39fa544..3228f4ab2 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -11,6 +11,7 @@ from pydantic import model_validator from metagpt.actions import Action, UserRequirement from metagpt.actions.analyze_requirements import AnalyzeRequirementsRestrictions from metagpt.actions.di.run_command import RunCommand +from metagpt.actions.search_enhanced_qa import SearchEnhancedQA from metagpt.exp_pool import exp_cache from metagpt.exp_pool.context_builders import RoleZeroContextBuilder from metagpt.exp_pool.serializers import RoleZeroSerializer @@ -196,7 +197,7 @@ class RoleZero(Role): if not self.browser.is_empty_page: pattern = re.compile(r"Command Browser\.(\w+) executed") for index, msg in zip(range(len(memory), 0, -1), memory[::-1]): - if pattern.match(msg.content): + if pattern.search(msg.content): memory.insert(index, UserMessage(cause_by="browser", content=await self.browser.view())) break return memory @@ -225,7 +226,7 @@ class RoleZero(Role): self._set_state(0) # problems solvable by quick thinking doesn't need to a formal think-act cycle - quick_rsp = await self._quick_think() + quick_rsp, _ = await self._quick_think() if quick_rsp: return quick_rsp @@ -245,22 +246,28 @@ class RoleZero(Role): actions_taken += 1 return rsp # return output from the last action - async def _quick_think(self) -> Message: + async def _quick_think(self) -> Tuple[Message, str]: + answer = "" rsp_msg = None 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 + return rsp_msg, "" # routing - memory = self.get_memories(k=4) + memory = self.get_memories(k=4) # FIXME: A magic number for two rounds of Q&A context = self.llm.format_msg(memory + [UserMessage(content=QUICK_THINK_PROMPT)]) - rsp = await self.llm.aask(context) + intent_result = await self.llm.aask(context) - if "yes" in rsp.lower(): + if "YES" in intent_result: # llm call with the original context async with ThoughtReporter(enable_llm_stream=True) as reporter: await reporter.async_report({"type": "quick"}) answer = await self.llm.aask(self.llm.format_msg(memory)) + elif "SEARCH" in intent_result: + query = "\n".join(str(msg) for msg in memory) + answer = await SearchEnhancedQA().run(query) + + if answer: self.rc.memory.add(AIMessage(content=answer, cause_by=RunCommand)) await self.reply_to_human(content=answer) rsp_msg = AIMessage( @@ -269,7 +276,7 @@ class RoleZero(Role): cause_by=RunCommand, ) - return rsp_msg + return rsp_msg, intent_result async def _check_duplicates(self, req: list[dict], command_rsp: str): past_rsp = [mem.content for mem in self.rc.memory.get(self.memory_k)] diff --git a/tests/metagpt/roles/di/test_routing.py b/tests/metagpt/roles/di/test_routing.py index c476fe9ad..e333bfac1 100644 --- a/tests/metagpt/roles/di/test_routing.py +++ b/tests/metagpt/roles/di/test_routing.py @@ -19,18 +19,41 @@ NORMAL_QUESTION = [ and save it to a csv file. paper title must include `multiagent` or `large language model`. *notice: print key variables* """, """ + Get products data from website https://scrapeme.live/shop/ and save it as a csv file. + The first page product name, price, product URL, and image URL must be saved in the csv;** + """, + """ 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 """, - ## 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的健身房。""", - "今天的天气怎样", - "奥运会的开幕式是什么时候", + "Open this link and make a sumamry: https://github.com/geekan/MetaGPT", # should not confuse with searching + "清查看这个网页https://platform.openai.com/docs/models", # should not confuse with searching ] + +SEARCH_QUESTION = [ + "今天的天气怎样?", + "全球智能手机市场份额排名是什么?前三名的品牌各占多少百分比?", + "中国股市上市公司数量是多少?", + "奥运会将在哪里举行?有哪些新增的比赛项目?", + "最近一周全球原油价格的走势如何?", + "当前全球碳排放量最大的三个国家是哪些?", + "当前全球碳排放量最大的三个国家各占多少比例", + "最新的全球教育质量排名中,前五名的国家是哪些?", + "当前全球最大的几家电动汽车制造商是哪些?", + "奥运会的开幕式是什么时候", + "Recommend some gyms near Shenzhen University", + "Which university tops QS ranking?", + "Which university tops QS ranking this year?", + "The stock price of Nvidia?", + # longer questions + "请为我查找位于深圳大学附近1000米范围内,价格适中(性价比最高),且晚上关门时间晚于22:00的健身房。", + "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", +] + + QUICK_QUESTION = [ ## general knowledge qa, logical, math ## """Who is the first man landing on Moon""", @@ -102,22 +125,28 @@ 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() + rsp, intent_result = await role._quick_think() role.rc.memory.clear() - if not rsp: + if "YES" not in intent_result: logger.error(f"Quick question failed: {q}") - # assert rsp + + for q in SEARCH_QUESTION: + msg = Message(content=q) + role.put_message(msg) + await role._observe() + rsp, intent_result = await role._quick_think() + role.rc.memory.clear() + if "SEARCH" not in intent_result: + logger.error(f"Search question failed: {q}") for q in NORMAL_QUESTION: msg = Message(content=q) role.put_message(msg) await role._observe() - rsp = await role._quick_think() + rsp, intent_result = await role._quick_think() role.rc.memory.clear() - # assert not rsp - if rsp: + if "NO" not in intent_result: logger.error(f"Normal question failed: {q}") From dbdfd29cea5bdd7ef87c16160bd217a61f4ad7d6 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Sun, 4 Aug 2024 17:33:04 +0800 Subject: [PATCH 2/6] equip engineer2 with terminal --- metagpt/prompts/di/role_zero.py | 2 +- metagpt/roles/di/engineer2.py | 8 +++++++- metagpt/roles/di/role_zero.py | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 721fe7e60..9b467bafd 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -115,7 +115,7 @@ Decide if the latest user message previously is a quick question. Quick questions include common-sense, legal, 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. Time- or location-sensitive questions such as wheather or news inquiry are NOT quick questions. Moreover, you should output a keyword SEARCH to indicate the need for a google search. -Software development tasks are NOT quick questions. +Software development tasks are NOT quick questions. Code execution, however trivial, is NOT a quick question. 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 or a SEARCH. Your response: """ diff --git a/metagpt/roles/di/engineer2.py b/metagpt/roles/di/engineer2.py index fbade0421..f6c3a6658 100644 --- a/metagpt/roles/di/engineer2.py +++ b/metagpt/roles/di/engineer2.py @@ -1,9 +1,12 @@ from __future__ import annotations +from pydantic import Field + from metagpt.actions.write_code_review import ReviewAndRewriteCode from metagpt.prompts.di.engineer2 import ENGINEER2_INSTRUCTION from metagpt.roles.di.role_zero import RoleZero from metagpt.strategy.experience_retriever import ENGINEER_EXAMPLE +from metagpt.tools.libs.terminal import Terminal class Engineer2(RoleZero): @@ -12,13 +15,16 @@ class Engineer2(RoleZero): goal: str = "Take on game, app, and web development." instruction: str = ENGINEER2_INSTRUCTION - tools: list[str] = ["Plan", "Editor:write,read", "RoleZero", "ReviewAndRewriteCode"] + terminal: Terminal = Field(default_factory=Terminal, exclude=True) + + tools: list[str] = ["Plan", "Editor:write,read", "RoleZero", "Terminal:run_command", "ReviewAndRewriteCode"] def _update_tool_execution(self): review = ReviewAndRewriteCode() self.tool_execution_map.update( { + "Terminal.run_command": self.terminal.run_command, "ReviewAndRewriteCode.run": review.run, "ReviewAndRewriteCode": review.run, } diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 3228f4ab2..4e932649c 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -64,7 +64,6 @@ class RoleZero(Role): # Equipped with three basic tools by default for optional use editor: Editor = Editor() browser: Browser = Browser() - # terminal: Terminal = Terminal() # FIXME: TypeError: cannot pickle '_thread.lock' object # Experience experience_retriever: ExpRetriever = DummyExpRetriever() From a4934403f0a6620d207a2faa6d613960521ec05c Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Sun, 4 Aug 2024 17:33:22 +0800 Subject: [PATCH 3/6] fix write prd bug --- metagpt/actions/write_prd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index a062ece54..f66d586a3 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -313,7 +313,7 @@ class WritePRD(Action): if not output_pathname: output_pathname = DEFAULT_WORKSPACE_ROOT / "docs" / "prd.json" - output_pathname.mkdir(parents=True, exist_ok=True) + output_pathname.parent.mkdir(parents=True, exist_ok=True) elif not Path(output_pathname).is_absolute(): output_pathname = DEFAULT_WORKSPACE_ROOT / output_pathname output_pathname = Path(output_pathname) From 24d472a8a81a800fde8e7642db0445eb50f8bb57 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Sun, 4 Aug 2024 17:35:51 +0800 Subject: [PATCH 4/6] make engineer2, not sweagent, to fix his own repo --- metagpt/prompts/di/team_leader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 2f3e69651..e263775c6 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -20,7 +20,7 @@ Note: 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. The software default development process has four steps: creating a Product Requirement Document (PRD) by the Product Manager -> writing a System Design by the Architect -> creating tasks by the Project Manager -> and coding by the Engineer. You may choose to execute any of these steps. When publishing message to Product Manager, you should directly copy the full original user requirement. 2.1. 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. -3. If the requirement is to fix a bug or issue, you should assign it to Issue Solver instead of Engineer. +3. If the requirement is to fix a bug or issue, you should assign it to Issue Solver instead of Engineer. However, if the bug or issue is related to the software developed by the team, you should assign it to Engineer. 4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members. 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. From 488536aa16bd30bbc39f72a9e2d3becbc476cfe4 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Mon, 5 Aug 2024 11:16:27 +0800 Subject: [PATCH 5/6] fix typo --- tests/metagpt/roles/di/test_routing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/metagpt/roles/di/test_routing.py b/tests/metagpt/roles/di/test_routing.py index e333bfac1..0cd94e571 100644 --- a/tests/metagpt/roles/di/test_routing.py +++ b/tests/metagpt/roles/di/test_routing.py @@ -28,7 +28,7 @@ NORMAL_QUESTION = [ checkout a branch named test-fix, commit your changes, push, and create a PR to the master branch of https://github.com/iorisa/langchain """, "Open this link and make a sumamry: https://github.com/geekan/MetaGPT", # should not confuse with searching - "清查看这个网页https://platform.openai.com/docs/models", # should not confuse with searching + "请查看这个网页https://platform.openai.com/docs/models", # should not confuse with searching ] From d2f53307441dbe88b79277619101b5feb0a00577 Mon Sep 17 00:00:00 2001 From: garylin2099 Date: Mon, 5 Aug 2024 11:28:43 +0800 Subject: [PATCH 6/6] rm unnecessary mkdir and fixed a bug --- metagpt/actions/design_api.py | 1 - metagpt/actions/project_management.py | 1 - metagpt/actions/write_prd.py | 1 - 3 files changed, 3 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 3eb8fe04d..30dd14a2f 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -267,7 +267,6 @@ class WriteDesign(Action): if not output_pathname: output_pathname = Path(output_pathname) / "docs" / "sytem_design.json" - output_pathname.mkdir(parents=True, exist_ok=True) elif not Path(output_pathname).is_absolute(): output_pathname = DEFAULT_WORKSPACE_ROOT / output_pathname output_pathname = Path(output_pathname) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 355a3853d..c9ba65682 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -188,7 +188,6 @@ class WriteTasks(Action): if not output_pathname: output_pathname = Path(output_pathname) / "docs" / "project_schedule.json" - output_pathname.mkdir(parents=True, exist_ok=True) elif not Path(output_pathname).is_absolute(): output_pathname = DEFAULT_WORKSPACE_ROOT / output_pathname output_pathname = Path(output_pathname) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index f66d586a3..e008d3a3b 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -313,7 +313,6 @@ class WritePRD(Action): if not output_pathname: output_pathname = DEFAULT_WORKSPACE_ROOT / "docs" / "prd.json" - output_pathname.parent.mkdir(parents=True, exist_ok=True) elif not Path(output_pathname).is_absolute(): output_pathname = DEFAULT_WORKSPACE_ROOT / output_pathname output_pathname = Path(output_pathname)