From f9b0262c555639dc126a6f85f369ff4d025ceffb Mon Sep 17 00:00:00 2001 From: liushaojie Date: Fri, 25 Oct 2024 10:04:04 +0800 Subject: [PATCH 1/4] =?UTF-8?q?update:=20=E8=BD=AF=E4=BB=B6=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/search_enhanced_qa.py | 4 +- metagpt/prompts/product_manager.py | 175 ++++++++++++++++++ metagpt/roles/product_manager.py | 50 ++--- metagpt/schema.py | 12 +- tests/metagpt/roles/di/run_product_manager.py | 82 +++++++- 5 files changed, 281 insertions(+), 42 deletions(-) create mode 100644 metagpt/prompts/product_manager.py diff --git a/metagpt/actions/search_enhanced_qa.py b/metagpt/actions/search_enhanced_qa.py index 1427f9b19..b1810f06a 100644 --- a/metagpt/actions/search_enhanced_qa.py +++ b/metagpt/actions/search_enhanced_qa.py @@ -209,7 +209,7 @@ class SearchEnhancedQA(Action): list[str]: Summaries of relevant web content. """ - relevant_urls = await self._collect_relevant_links(query) + relevant_urls = await self.collect_relevant_links(query) await self._reporter.async_report({"type": "search", "stage": "searching", "urls": relevant_urls}) if not relevant_urls: logger.warning(f"No relevant URLs found for query: {query}") @@ -226,7 +226,7 @@ class SearchEnhancedQA(Action): return citations - async def _collect_relevant_links(self, query: str) -> list[str]: + async def collect_relevant_links(self, query: str) -> list[str]: """Search and rank URLs relevant to the query. Args: diff --git a/metagpt/prompts/product_manager.py b/metagpt/prompts/product_manager.py new file mode 100644 index 000000000..0fc7c3557 --- /dev/null +++ b/metagpt/prompts/product_manager.py @@ -0,0 +1,175 @@ +from metagpt.prompts.di.role_zero import ROLE_INSTRUCTION + +EXTRA_INSTRUCTION = """ +You are a product manager AI assistant specializing in product requirement documentation and market research analysis. +Your work focuses on the analysis of problems and data. +You should always output a document. + +## Core Tools +1. Editor: For the creation and modification of `PRD/Research Report` documents. +2. SearchEnhancedQA: The specified tool for collecting information from the internet MUST BE USED for searching. +3. Browser: Access the search results provided by the SearchEnhancedQA tool using the "goto" method. + +## Mode 1: PRD Creation +Triggered by software/product requests or feature enhancements, ending with the output of a complete PRD. + +### Required Fields +1. Language & Project Info + - Language: Match user's language + - Programming Language: Default to native HTML if unspecified + - Project Name: Use snake_case format + - Restate the original requirements + +2. Product Definition(**IMPORTANT** ) + - Product Goals: 3 clear, orthogonal goals + - User Stories: 3-5 scenarios in "As a [role], I want [feature] so that [benefit]" format + - Competitive Analysis: 5-7 products with pros/cons + - Competitive Quadrant Chart(Required): Using Mermaid + +3. Technical Specifications + - Requirements Analysis: Comprehensive overview of technical needs + - Requirements Pool: List with P0/P1/P2 priorities + - UI Design Draft: Basic layout and functionality + - Open Questions: Unclear aspects needing clarification + +#### Mermaid Diagram Rules +1. Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1 +2. Example: +```mermaid +quadrantChart + title "Reach and engagement of campaigns" + x-axis "Low Reach" --> "High Reach" + y-axis "Low Engagement" --> "High Engagement" + quadrant-1 "We should expand" + quadrant-2 "Need to promote" + quadrant-3 "Re-evaluate" + quadrant-4 "May be improved" + "Campaign A": [0.3, 0.6] + "Campaign B": [0.45, 0.23] + "Campaign C": [0.57, 0.69] + "Campaign D": [0.78, 0.34] + "Campaign E": [0.40, 0.34] + "Campaign F": [0.35, 0.78] + "Our Target Product": [0.5, 0.6] +``` + +### PRD Document Guidelines +- Use clear requirement language (Must/Should/May) +- Include measurable criteria +- Prioritize clearly (P0: Must-have, P1: Should-have, P2: Nice-to-have) +- Support with diagrams and charts +- Focus on user value and business goals + +## Mode 2: Market Research +Triggered by market analysis or competitor research requests, ending with the output of a complete report document. + +### **IMPORTANT** Information Collection Requirements + +Must follow this strict information gathering process: +1. Keyword Generation Rules: + - Infer 3 distinct keyword groups on user needs. + - Each group must be a space-separated phrase containing: + * Target industry/product name (REQUIRED) + * Specific aspect or metric + * Time frame or geographic scope when relevant + + Example format: + - Group 1: "electric vehicles market size forecast 2024" + - Group 2: "electric vehicles manufacturing costs analysis" + - Group 3: "electric vehicles consumer preferences survey" + +2. Search Process: + - For each keyword: + * Use SearchEnhancedQA TOOL (SearchEnhancedQA.collect_relevant_links) collect top 3 search results + * Remove duplicate URLs + +3. Information Analysis: + - Must read and analyze EACH unique source individually + - Synthesize information across all sources + - Cross-reference and verify key data points + - Identify critical insights and trends + +4. Quality Control: + - Verify data consistency across sources + - Fill information gaps with targeted additional research + - Ensure balanced perspective from multiple sources + + +### Report Structure +1. Summary: Key findings and recommendations +2. Industry Overview: Market size, trends, and structure +3. Market Analysis: Segments, growth drivers, and challenges +4. Competitor Landscape: Key players and positioning +5. Target Audience Analysis: User segments and needs +6. Pricing Analysis: Market rates and strategies +7. Key Findings: Major insights and opportunities +8. Strategic Recommendations: Action items +9. Appendices: Supporting data + + +### Final Report Requirements +1. Report must be entirely focused on insights and analysis: + - No mention of research methodology + - No source tracking or process documentation + - Present only validated findings and conclusions + +2. Professional Format: + - Clear section hierarchy + - Rich subsection content + - Evidence-based analysis + - Data visualization where appropriate + +3. Content Depth Requirements: + Executive Summary (500+ words): + - Key Market Metrics + - Critical Findings + - Strategic Recommendations + + Industry Overview (800+ words): + - Market Size and Growth + - Industry Value Chain + - Regulatory Environment + - Technology Trends + +4. Quality Standards: + - Every main section must have 3+ detailed subsections + - Each subsection requires 200-300 words minimum + - Include specific examples and data points + - Support all major claims with market evidence + +### Research Guidelines +- Base all analysis on collected data +- Include quantitative and qualitative insights +- Support claims with evidence +- Maintain professional formatting +- Use visuals to support key points + +## Document Standards +1. Format + - Clear heading hierarchy + - Consistent markdown formatting + - Numbered sections + - Professional graphics + - Output charts using Mermaid syntax + +2. Content + - Objective analysis + - Actionable insights + - Clear recommendations + - Supporting evidence + +3. Quality Checks + - Verify data accuracy + - Cross-reference sources + - Ensure completeness + - Review clarity + +Remember: +- Always start with thorough requirements analysis +- Use appropriate tools for each task +- Keep recommendations actionable +- Consider all stakeholder perspectives +- Maintain professional standards throughout +""" + +PRODUCT_MANAGER_INSTRUCTION = ROLE_INSTRUCTION + EXTRA_INSTRUCTION.strip() diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index f2a47affd..e91ad86e7 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -4,16 +4,14 @@ @Time : 2023/5/11 14:43 @Author : alexanderwu @File : product_manager.py -@Modified By: mashenquan, 2023/11/27. Add `PrepareDocuments` action according to Section 2.2.3.5.1 of RFC 135. +@Modified By: liushaojie, 2024/10/17. """ - -from metagpt.actions import UserRequirement, WritePRD -from metagpt.actions.prepare_documents import PrepareDocuments -from metagpt.actions.requirement_analysis.requirement.pic2txt import Pic2Txt +from metagpt.actions.search_enhanced_qa import SearchEnhancedQA +from metagpt.prompts.product_manager import PRODUCT_MANAGER_INSTRUCTION from metagpt.roles.di.role_zero import RoleZero -from metagpt.roles.role import RoleReactMode -from metagpt.utils.common import any_to_name, any_to_str, tool2name -from metagpt.utils.git_repository import GitRepository +from metagpt.tools.libs.browser import Browser +from metagpt.tools.libs.editor import Editor +from metagpt.utils.common import tool2name class ProductManager(RoleZero): @@ -29,38 +27,18 @@ class ProductManager(RoleZero): name: str = "Alice" profile: str = "Product Manager" - goal: str = "efficiently create a successful product that meets market demands and user expectations. Create a Product Requirement Document." + goal: str = "Create a Product Requirement Document or market research/competitive product research." 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, 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] = ["RoleZero", "WritePRD", Pic2Txt.__name__] + instruction: str = PRODUCT_MANAGER_INSTRUCTION + max_react_loop: int = 50 + tools: list[str] = ["RoleZero", Browser.__name__, Editor.__name__, SearchEnhancedQA.__name__] def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - # NOTE: The following init setting will only be effective when self.use_fixed_sop is changed to True self.enable_memory = False - self.set_actions([PrepareDocuments(send_to=any_to_str(self)), WritePRD]) - if self.use_fixed_sop: - self._watch([UserRequirement, PrepareDocuments]) - self.rc.react_mode = RoleReactMode.BY_ORDER def _update_tool_execution(self): - wp = WritePRD() - self.tool_execution_map.update(tool2name(WritePRD, ["run"], wp.run)) - pic2txt = Pic2Txt() - self.tool_execution_map.update(tool2name(Pic2Txt, ["run"], pic2txt.run)) - - async def _think(self) -> bool: - """Decide what to do""" - if not self.use_fixed_sop: - return await super()._think() - - if GitRepository.is_git_dir(self.config.project_path) and not self.config.git_reinit: - self._set_state(1) - else: - self._set_state(0) - self.config.git_reinit = False - self.todo_action = any_to_name(WritePRD) - return bool(self.rc.todo) + se_qa = SearchEnhancedQA() + self.tool_execution_map.update( + tool2name(SearchEnhancedQA, ["collect_relevant_links"], se_qa.collect_relevant_links) + ) diff --git a/metagpt/schema.py b/metagpt/schema.py index 5224eaf14..24ca5b14f 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -685,7 +685,17 @@ class Plan(BaseModel): def append_task( self, task_id: str, dependent_task_ids: list[str], instruction: str, assignee: str, task_type: str = "" ): - """Append a new task with task_id (number) to the end of existing task sequences. If dependent_task_ids is not empty, the task will depend on the tasks with the ids in the list. Note that the assignee should be the 'name' of the role.""" + """ + Append a new task with task_id (number) to the end of existing task sequences. + If dependent_task_ids is not empty, the task will depend on the tasks with the ids in the list. + Note that the assignee should be the 'name' of the role. + Args: + task_id (str): The task id to be appended to the existing task sequence + dependent_task_ids (list[str]): The task ids that the new task depends on + instruction (str): The instruction of the new task + assignee (str): The assignee of the new task + task_type (str): The type of the new task, default is empty string + """ new_task = Task( task_id=task_id, dependent_task_ids=dependent_task_ids, diff --git a/tests/metagpt/roles/di/run_product_manager.py b/tests/metagpt/roles/di/run_product_manager.py index 3ab1e9bab..42aa7840a 100644 --- a/tests/metagpt/roles/di/run_product_manager.py +++ b/tests/metagpt/roles/di/run_product_manager.py @@ -1,13 +1,72 @@ import asyncio +import sys -from metagpt.roles.product_manager import ProductManager +from metagpt.logs import logger +from metagpt.roles import ProductManager WRITE_2048 = """Write a PRD for a cli 2048 game""" -REWRITE_2048 = """Rewrite the prd at /Users/gary/Files/temp/workspace/2048_game/docs/prd.json, add a web UI""" +# REWRITE_2048 = """Rewrite the prd at /Users/gary/Files/temp/workspace/2048_game/docs/prd.json, add a web UI""" CASUAL_CHAT = """What's your name?""" +CASE1_GREEDY_SNAKE = "设计一个贪吃蛇游戏,root path: '/Users/seeker/Projects/sdfz/mg/mgx_ops/workspace/temp'" +CASE2_SMART_HOME = "搜索并分析米家、华为智能家居和海尔智家在智能家居市场中的功能、用户需求和市场定位" +CASE3_BEST_SELLING_REFRIGERATOR = "调研当前市场上最畅销的智能冰箱的五个关键特性" + +OLD_PRD = """ +Language +en_us + +Programming Language +N/A + +Original Requirements +Write a PRD based on the current music streaming service. + +Project Name +music_streaming_service + +Product Goals +Enhance user experience with seamless music streaming +Improve accessibility and responsiveness across devices +Expand music library and personalized recommendations +User Stories +As a user, I want to easily search and find my favorite songs and artists. +As a user, I want to create and manage my own playlists. +As a user, I want to receive personalized music recommendations based on my listening history. +As a user, I want to stream music without interruptions or buffering. +As a user, I want to access the service on both desktop and mobile devices. +Competitive Analysis +Spotify: Extensive music library, strong personalized recommendations, and cross-platform availability. +Apple Music: High-quality audio, exclusive content, and seamless integration with Apple devices. +Amazon Music: Large music catalog, integration with Amazon Echo devices, and competitive pricing. +YouTube Music: Vast collection of music videos, user-generated content, and strong search capabilities. +Tidal: High-fidelity sound quality, exclusive releases, and artist-centric approach. +Competitive Quadrant Chart +quadrantChart title "Feature Richness vs. User Satisfaction" x-axis "Low Feature Richness" --> "High Feature Richness" y-axis "Low User Satisfaction" --> "High User Satisfaction" quadrant-1 "Market Leaders" quadrant-2 "Potential Growth" quadrant-3 "Needs Improvement" quadrant-4 "Niche Players" "Spotify": [0.9, 0.85] "Apple Music": [0.85, 0.8] "Amazon Music": [0.75, 0.7] "YouTube Music": [0.8, 0.75] "Tidal": [0.7, 0.65] "Our Target Product": [0.8, 0.8] + +Requirement Analysis +The current music streaming service needs to focus on enhancing user experience by providing seamless streaming, improving accessibility, and expanding the music library. Personalized recommendations and cross-platform availability are crucial for user retention. + +Requirement Pool +['P0', 'Implement a robust search functionality to find songs and artists easily.'] +['P0', 'Develop a feature for users to create and manage playlists.'] +['P1', 'Enhance the recommendation algorithm for personalized music suggestions.'] +['P1', 'Optimize the streaming service to minimize interruptions and buffering.'] +['P2', 'Ensure the service is fully responsive and accessible on both desktop and mobile devices.'] +UI Design draft +The UI should be clean and intuitive, with a prominent search bar, easy-to-navigate menus for playlists and recommendations, and a responsive design that adapts to different screen sizes. The player controls should be easily accessible, and the overall aesthetic should be modern and visually appealing. + +Anything UNCLEAR +Currently, all aspects of the project are clear. +""" +CASE4_MUSIC_STREAMING_MEDIA = f"""We have received feedback from users regarding the current music streaming service, stating that they need better personalized recommendations. Please readjust the content of PRD {OLD_PRD} based on these feedback.""" + +CASE5_SMART_BIG_SCREEN = """分析2024年上半年中国家庭智能大屏行业的发展情况并输出市场分析报告""" + +# CASE6 = "我想要生产一个电子烟产品,请帮我完成市场调研分析报告" + async def main(requirement): product_manager = ProductManager() @@ -15,4 +74,21 @@ async def main(requirement): if __name__ == "__main__": - asyncio.run(main(WRITE_2048)) + cases = [ + # CASE1_GREEDY_SNAKE, + # CASE2_SMART_HOME, + CASE3_BEST_SELLING_REFRIGERATOR, + # CASE4_MUSIC_STREAMING_MEDIA, + # CASE5_SMART_BIG_SCREEN, + # CASE6, + # WRITE_2048, + ] + logger.remove() + # logger.add(sys.stderr, level="DEBUG") + logger.add(sys.stderr, level="INFO") + for case in cases: + logger.info(f"user requirement:\n{case}") + try: + asyncio.run(main(case)) + except Exception as e: + print(e) From b51dbcd26096342f1e5fe72e40cc78b20bca456b Mon Sep 17 00:00:00 2001 From: seeker-jie Date: Fri, 25 Oct 2024 14:28:09 +0800 Subject: [PATCH 2/4] =?UTF-8?q?update:=20=E6=9B=B4=E6=96=B0=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E9=9C=80=E6=B1=82=E9=BB=98=E8=AE=A4=E7=BC=96=E7=A8=8B?= =?UTF-8?q?=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/prompts/product_manager.py | 2 +- tests/metagpt/roles/test_product_manager.py | 50 --------------------- 2 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 tests/metagpt/roles/test_product_manager.py diff --git a/metagpt/prompts/product_manager.py b/metagpt/prompts/product_manager.py index 0fc7c3557..b77c9051b 100644 --- a/metagpt/prompts/product_manager.py +++ b/metagpt/prompts/product_manager.py @@ -16,7 +16,7 @@ Triggered by software/product requests or feature enhancements, ending with the ### Required Fields 1. Language & Project Info - Language: Match user's language - - Programming Language: Default to native HTML if unspecified + - Programming Language: If not specified in the requirements, use Vite, React, MUI, Tailwind CSS. - Project Name: Use snake_case format - Restate the original requirements diff --git a/tests/metagpt/roles/test_product_manager.py b/tests/metagpt/roles/test_product_manager.py deleted file mode 100644 index 9a28a3296..000000000 --- a/tests/metagpt/roles/test_product_manager.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@Time : 2023/5/16 14:50 -@Author : alexanderwu -@File : test_product_manager.py -""" -import json - -import pytest - -from metagpt.actions import WritePRD -from metagpt.const import REQUIREMENT_FILENAME -from metagpt.context import Context -from metagpt.logs import logger -from metagpt.roles import ProductManager -from metagpt.utils.common import any_to_str -from tests.metagpt.roles.mock import MockMessages - - -@pytest.mark.asyncio -async def test_product_manager(new_filename): - context = Context() - try: - assert context.git_repo is None - assert context.repo is None - product_manager = ProductManager(context=context) - # prepare documents - rsp = await product_manager.run(MockMessages.req) - assert context.git_repo - assert context.repo - assert rsp.cause_by == any_to_str(WritePRD) - assert REQUIREMENT_FILENAME in context.repo.docs.changed_files - logger.info(rsp) - assert len(rsp.content) > 0 - doc = list(rsp.instruct_content.docs.values())[0] - m = json.loads(doc.content) - assert m["Original Requirements"] == MockMessages.req.content - - # nothing to do - rsp = await product_manager.run(rsp) - assert rsp is None - except Exception as e: - assert not e - finally: - context.git_repo.delete_repository() - - -if __name__ == "__main__": - pytest.main([__file__, "-s"]) From 76e641ab1ea958985f05d48285f0c96972a0e7be Mon Sep 17 00:00:00 2001 From: seeker-jie Date: Tue, 29 Oct 2024 19:39:26 +0800 Subject: [PATCH 3/4] =?UTF-8?q?update:=20=E6=A0=B9=E6=8D=AE=20CR=20?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/search_enhanced_qa.py | 4 +- metagpt/prompts/product_manager.py | 6 +- metagpt/roles/product_manager.py | 33 +++++++++-- metagpt/schema.py | 2 +- tests/metagpt/roles/di/run_product_manager.py | 57 ++++++++----------- 5 files changed, 58 insertions(+), 44 deletions(-) diff --git a/metagpt/actions/search_enhanced_qa.py b/metagpt/actions/search_enhanced_qa.py index b1810f06a..1427f9b19 100644 --- a/metagpt/actions/search_enhanced_qa.py +++ b/metagpt/actions/search_enhanced_qa.py @@ -209,7 +209,7 @@ class SearchEnhancedQA(Action): list[str]: Summaries of relevant web content. """ - relevant_urls = await self.collect_relevant_links(query) + relevant_urls = await self._collect_relevant_links(query) await self._reporter.async_report({"type": "search", "stage": "searching", "urls": relevant_urls}) if not relevant_urls: logger.warning(f"No relevant URLs found for query: {query}") @@ -226,7 +226,7 @@ class SearchEnhancedQA(Action): return citations - async def collect_relevant_links(self, query: str) -> list[str]: + async def _collect_relevant_links(self, query: str) -> list[str]: """Search and rank URLs relevant to the query. Args: diff --git a/metagpt/prompts/product_manager.py b/metagpt/prompts/product_manager.py index b77c9051b..1bc210740 100644 --- a/metagpt/prompts/product_manager.py +++ b/metagpt/prompts/product_manager.py @@ -32,7 +32,7 @@ Triggered by software/product requests or feature enhancements, ending with the - UI Design Draft: Basic layout and functionality - Open Questions: Unclear aspects needing clarification -#### Mermaid Diagram Rules +#### Mermaid Diagram Rules 1. Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1 2. Example: ```mermaid @@ -67,7 +67,7 @@ Triggered by market analysis or competitor research requests, ending with the ou Must follow this strict information gathering process: 1. Keyword Generation Rules: - - Infer 3 distinct keyword groups on user needs. + - Infer 3 distinct keyword groups on user needs(Infer directly instead of using tools). - Each group must be a space-separated phrase containing: * Target industry/product name (REQUIRED) * Specific aspect or metric @@ -80,7 +80,7 @@ Must follow this strict information gathering process: 2. Search Process: - For each keyword: - * Use SearchEnhancedQA TOOL (SearchEnhancedQA.collect_relevant_links) collect top 3 search results + * Use SearchEnhancedQA TOOL (SearchEnhancedQA.run) collect top 3 search results * Remove duplicate URLs 3. Information Analysis: diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index e91ad86e7..334f36f52 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -6,12 +6,16 @@ @File : product_manager.py @Modified By: liushaojie, 2024/10/17. """ +from metagpt.actions import UserRequirement, WritePRD +from metagpt.actions.prepare_documents import PrepareDocuments from metagpt.actions.search_enhanced_qa import SearchEnhancedQA from metagpt.prompts.product_manager import PRODUCT_MANAGER_INSTRUCTION from metagpt.roles.di.role_zero import RoleZero +from metagpt.roles.role import RoleReactMode from metagpt.tools.libs.browser import Browser from metagpt.tools.libs.editor import Editor -from metagpt.utils.common import tool2name +from metagpt.utils.common import any_to_name, any_to_str, tool2name +from metagpt.utils.git_repository import GitRepository class ProductManager(RoleZero): @@ -33,12 +37,29 @@ class ProductManager(RoleZero): max_react_loop: int = 50 tools: list[str] = ["RoleZero", Browser.__name__, Editor.__name__, SearchEnhancedQA.__name__] + todo_action: str = any_to_name(WritePRD) + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.enable_memory = False + if self.use_fixed_sop: + self.enable_memory = False + self.set_actions([PrepareDocuments(send_to=any_to_str(self)), WritePRD]) + self._watch([UserRequirement, PrepareDocuments]) + self.rc.react_mode = RoleReactMode.BY_ORDER def _update_tool_execution(self): - se_qa = SearchEnhancedQA() - self.tool_execution_map.update( - tool2name(SearchEnhancedQA, ["collect_relevant_links"], se_qa.collect_relevant_links) - ) + wp = WritePRD() + self.tool_execution_map.update(tool2name(WritePRD, ["run"], wp.run)) + + async def _think(self) -> bool: + """Decide what to do""" + if not self.use_fixed_sop: + return await super()._think() + + if GitRepository.is_git_dir(self.config.project_path) and not self.config.git_reinit: + self._set_state(1) + else: + self._set_state(0) + self.config.git_reinit = False + self.todo_action = any_to_name(WritePRD) + return bool(self.rc.todo) diff --git a/metagpt/schema.py b/metagpt/schema.py index 24ca5b14f..70933d9bc 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -694,7 +694,7 @@ class Plan(BaseModel): dependent_task_ids (list[str]): The task ids that the new task depends on instruction (str): The instruction of the new task assignee (str): The assignee of the new task - task_type (str): The type of the new task, default is empty string + task_type (str, optional): The type of the new task, default is empty string """ new_task = Task( task_id=task_id, diff --git a/tests/metagpt/roles/di/run_product_manager.py b/tests/metagpt/roles/di/run_product_manager.py index 42aa7840a..bb230b7d9 100644 --- a/tests/metagpt/roles/di/run_product_manager.py +++ b/tests/metagpt/roles/di/run_product_manager.py @@ -4,16 +4,10 @@ import sys from metagpt.logs import logger from metagpt.roles import ProductManager -WRITE_2048 = """Write a PRD for a cli 2048 game""" - -# REWRITE_2048 = """Rewrite the prd at /Users/gary/Files/temp/workspace/2048_game/docs/prd.json, add a web UI""" - -CASUAL_CHAT = """What's your name?""" - -CASE1_GREEDY_SNAKE = "设计一个贪吃蛇游戏,root path: '/Users/seeker/Projects/sdfz/mg/mgx_ops/workspace/temp'" +CASE0_WRITE_2048 = """Write a PRD for a cli 2048 game""" +CASE1_GREEDY_SNAKE = "设计一个贪吃蛇游戏" CASE2_SMART_HOME = "搜索并分析米家、华为智能家居和海尔智家在智能家居市场中的功能、用户需求和市场定位" CASE3_BEST_SELLING_REFRIGERATOR = "调研当前市场上最畅销的智能冰箱的五个关键特性" - OLD_PRD = """ Language en_us @@ -62,33 +56,32 @@ Anything UNCLEAR Currently, all aspects of the project are clear. """ CASE4_MUSIC_STREAMING_MEDIA = f"""We have received feedback from users regarding the current music streaming service, stating that they need better personalized recommendations. Please readjust the content of PRD {OLD_PRD} based on these feedback.""" - CASE5_SMART_BIG_SCREEN = """分析2024年上半年中国家庭智能大屏行业的发展情况并输出市场分析报告""" - -# CASE6 = "我想要生产一个电子烟产品,请帮我完成市场调研分析报告" +CASE6_ELECTRONIC_CIGARETTE = """我想要生产一个电子烟产品,请帮我完成市场调研分析报告""" -async def main(requirement): - product_manager = ProductManager() - await product_manager.run(requirement) +def main(): + cases = [ + # CASE0_WRITE_2048, + # CASE1_GREEDY_SNAKE, + # CASE2_SMART_HOME, + # CASE3_BEST_SELLING_REFRIGERATOR, + # CASE4_MUSIC_STREAMING_MEDIA, + CASE5_SMART_BIG_SCREEN, + # CASE6_ELECTRONIC_CIGARETTE, + ] + root_path = "/tmp" + logger.remove() + logger.add(sys.stderr, level="INFO") + for case in cases: + case += f"\nroot path: '{root_path}'" + logger.info(f"user requirement:\n{case}") + try: + product_manager = ProductManager() + asyncio.run(product_manager.run(case)) + except Exception as e: + print(e) if __name__ == "__main__": - cases = [ - # CASE1_GREEDY_SNAKE, - # CASE2_SMART_HOME, - CASE3_BEST_SELLING_REFRIGERATOR, - # CASE4_MUSIC_STREAMING_MEDIA, - # CASE5_SMART_BIG_SCREEN, - # CASE6, - # WRITE_2048, - ] - logger.remove() - # logger.add(sys.stderr, level="DEBUG") - logger.add(sys.stderr, level="INFO") - for case in cases: - logger.info(f"user requirement:\n{case}") - try: - asyncio.run(main(case)) - except Exception as e: - print(e) + main() From 806853b223ad9acc5a1cfe7ee3739e373900295e Mon Sep 17 00:00:00 2001 From: seeker-jie Date: Tue, 29 Oct 2024 20:10:57 +0800 Subject: [PATCH 4/4] update --- metagpt/roles/product_manager.py | 1 - metagpt/schema.py | 6 --- tests/metagpt/roles/test_product_manager.py | 50 +++++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 tests/metagpt/roles/test_product_manager.py diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index 334f36f52..0f3613866 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -34,7 +34,6 @@ class ProductManager(RoleZero): goal: str = "Create a Product Requirement Document or market research/competitive product research." constraints: str = "utilize the same language as the user requirements for seamless communication" instruction: str = PRODUCT_MANAGER_INSTRUCTION - max_react_loop: int = 50 tools: list[str] = ["RoleZero", Browser.__name__, Editor.__name__, SearchEnhancedQA.__name__] todo_action: str = any_to_name(WritePRD) diff --git a/metagpt/schema.py b/metagpt/schema.py index 70933d9bc..52badcc21 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -689,12 +689,6 @@ class Plan(BaseModel): Append a new task with task_id (number) to the end of existing task sequences. If dependent_task_ids is not empty, the task will depend on the tasks with the ids in the list. Note that the assignee should be the 'name' of the role. - Args: - task_id (str): The task id to be appended to the existing task sequence - dependent_task_ids (list[str]): The task ids that the new task depends on - instruction (str): The instruction of the new task - assignee (str): The assignee of the new task - task_type (str, optional): The type of the new task, default is empty string """ new_task = Task( task_id=task_id, diff --git a/tests/metagpt/roles/test_product_manager.py b/tests/metagpt/roles/test_product_manager.py new file mode 100644 index 000000000..9a28a3296 --- /dev/null +++ b/tests/metagpt/roles/test_product_manager.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/16 14:50 +@Author : alexanderwu +@File : test_product_manager.py +""" +import json + +import pytest + +from metagpt.actions import WritePRD +from metagpt.const import REQUIREMENT_FILENAME +from metagpt.context import Context +from metagpt.logs import logger +from metagpt.roles import ProductManager +from metagpt.utils.common import any_to_str +from tests.metagpt.roles.mock import MockMessages + + +@pytest.mark.asyncio +async def test_product_manager(new_filename): + context = Context() + try: + assert context.git_repo is None + assert context.repo is None + product_manager = ProductManager(context=context) + # prepare documents + rsp = await product_manager.run(MockMessages.req) + assert context.git_repo + assert context.repo + assert rsp.cause_by == any_to_str(WritePRD) + assert REQUIREMENT_FILENAME in context.repo.docs.changed_files + logger.info(rsp) + assert len(rsp.content) > 0 + doc = list(rsp.instruct_content.docs.values())[0] + m = json.loads(doc.content) + assert m["Original Requirements"] == MockMessages.req.content + + # nothing to do + rsp = await product_manager.run(rsp) + assert rsp is None + except Exception as e: + assert not e + finally: + context.git_repo.delete_repository() + + +if __name__ == "__main__": + pytest.main([__file__, "-s"])