From 6755de0ee83b9bbd0de37e2027042fd78f354507 Mon Sep 17 00:00:00 2001 From: femto Date: Fri, 8 Sep 2023 12:12:33 +0800 Subject: [PATCH] ruff fix --- metagpt/actions/action.py | 25 ++-- metagpt/actions/design_api.py | 112 ++++++++--------- metagpt/actions/design_api_json.py | 129 ------------------- metagpt/actions/project_management.py | 19 +-- metagpt/actions/write_prd.py | 100 +++++++-------- metagpt/actions/write_prd_json.py | 135 -------------------- metagpt/document_store/lancedb_store.py | 45 ++++--- metagpt/roles/architect.py | 32 ++--- metagpt/roles/engineer.py | 74 +++++------ metagpt/roles/product_manager.py | 24 ++-- metagpt/roles/project_manager.py | 24 ++-- metagpt/utils/custom_decoder.py | 136 ++++++++++++--------- tests/metagpt/utils/test_custom_decoder.py | 8 +- 13 files changed, 290 insertions(+), 573 deletions(-) delete mode 100644 metagpt/actions/design_api_json.py delete mode 100644 metagpt/actions/write_prd_json.py diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index f2dcd86d4..709e24bfc 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -5,25 +5,21 @@ @Author : alexanderwu @File : action.py """ -import ast -import json +import re from abc import ABC from typing import Optional -import re from tenacity import retry, stop_after_attempt, wait_fixed - from metagpt.actions.action_output import ActionOutput from metagpt.llm import LLM +from metagpt.logs import logger from metagpt.utils.common import OutputParser from metagpt.utils.custom_decoder import CustomDecoder -from metagpt.logs import logger - class Action(ABC): - def __init__(self, name: str = '', context=None, llm: LLM = None): + def __init__(self, name: str = "", context=None, llm: LLM = None): self.name: str = name if llm is None: llm = LLM() @@ -54,9 +50,9 @@ class Action(ABC): return await self.llm.aask(prompt, system_msgs) @retry(stop=stop_after_attempt(2), wait=wait_fixed(1)) - async def _aask_v1(self, prompt: str, output_class_name: str, - output_data_mapping: dict, - system_msgs: Optional[list[str]] = None) -> ActionOutput: + async def _aask_v1( + self, prompt: str, output_class_name: str, output_data_mapping: dict, system_msgs: Optional[list[str]] = None + ) -> ActionOutput: """Append default prefix""" if not system_msgs: system_msgs = [] @@ -70,9 +66,9 @@ class Action(ABC): return ActionOutput(content, instruct_content) @retry(stop=stop_after_attempt(2), wait=wait_fixed(1)) - async def _aask_json_v1(self, prompt: str, output_class_name: str, - output_data_mapping: dict, - system_msgs: Optional[list[str]] = None) -> ActionOutput: + async def _aask_json_v1( + self, prompt: str, output_class_name: str, output_data_mapping: dict, system_msgs: Optional[list[str]] = None + ) -> ActionOutput: """Append default prefix""" if not system_msgs: system_msgs = [] @@ -81,7 +77,7 @@ class Action(ABC): logger.debug(content) output_class = ActionOutput.create_model_class(output_class_name, output_data_mapping) - pattern = r'\[CONTENT\](.*?)\[/CONTENT\]' + pattern = r"\[CONTENT\](.*?)\[/CONTENT\]" # Use re.findall to extract content between the tags extracted_content = re.search(pattern, content, re.DOTALL).group(1) @@ -94,4 +90,3 @@ class Action(ABC): async def run(self, *args, **kwargs): """Run action""" raise NotImplementedError("The run method should be implemented in a subclass.") - \ No newline at end of file diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index abd1f9d4c..8e2ca3306 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -25,7 +25,6 @@ PROMPT_TEMPLATE = """ Role: You are an architect; the goal is to design a SOTA PEP8-compliant python system; make the best use of good open source tools Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately Max Output: 8192 chars or 2048 tokens. Try to use them up. -Attention: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the code and triple quote. ## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework. @@ -39,45 +38,31 @@ Attention: Use '##' to split sections, not '#', and '## ' SHOULD W ## Anything UNCLEAR: Provide as Plain text. Make clear here. +Your job is to create a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example """ FORMAT_EXAMPLE = """ ---- -## Implementation approach -We will ... - -## Python package name -```python -"snake_game" -``` - -## File list -```python -[ - "main.py", -] -``` - -## Data structures and interface definitions -```mermaid -classDiagram - class Game{ - +int score - } - ... - Game "1" -- "1" Food: has -``` - -## Program call flow -```mermaid -sequenceDiagram - participant M as Main - ... - G->>M: end game -``` - -## Anything UNCLEAR -The requirement is clear to me. ---- +[CONTENT] +{ + "Implementation approach": "We will ...", + "Python package name": "snake_game", + "File list": ["main.py"], + "Data structures and interface definitions": ' + classDiagram + class Game{ + +int score + } + ... + Game "1" -- "1" Food: has + ', + "Program call flow": ' + sequenceDiagram + participant M as Main + ... + G->>M: end game + ', + "Anything UNCLEAR": "The requirement is clear to me." +} +[/CONTENT] """ OUTPUT_MAPPING = { "Implementation approach": (str, ...), @@ -92,9 +77,11 @@ OUTPUT_MAPPING = { class WriteDesign(Action): def __init__(self, name, context=None, llm=None): super().__init__(name, context, llm) - self.desc = "Based on the PRD, think about the system design, and design the corresponding APIs, " \ - "data structures, library tables, processes, and paths. Please provide your design, feedback " \ - "clearly and in detail." + self.desc = ( + "Based on the PRD, think about the system design, and design the corresponding APIs, " + "data structures, library tables, processes, and paths. Please provide your design, feedback " + "clearly and in detail." + ) def recreate_workspace(self, workspace: Path): try: @@ -103,42 +90,43 @@ class WriteDesign(Action): pass # Folder does not exist, but we don't care workspace.mkdir(parents=True, exist_ok=True) - def _save_prd(self, docs_path, resources_path, prd): - prd_file = docs_path / 'prd.md' - quadrant_chart = CodeParser.parse_code(block="Competitive Quadrant Chart", text=prd) - mermaid_to_file(quadrant_chart, resources_path / 'competitive_analysis') + def _save_prd(self, docs_path, resources_path, context): + prd_file = docs_path / "prd.md" + quadrant_chart = context[-1].instruct_content.dict()["Competitive Quadrant Chart"] + mermaid_to_file(quadrant_chart, resources_path / "competitive_analysis") logger.info(f"Saving PRD to {prd_file}") - prd_file.write_text(prd) + prd_file.write_text(context[-1].content) - def _save_system_design(self, docs_path, resources_path, content): - data_api_design = CodeParser.parse_code(block="Data structures and interface definitions", text=content) - seq_flow = CodeParser.parse_code(block="Program call flow", text=content) - mermaid_to_file(data_api_design, resources_path / 'data_api_design') - mermaid_to_file(seq_flow, resources_path / 'seq_flow') - system_design_file = docs_path / 'system_design.md' + def _save_system_design(self, docs_path, resources_path, system_design): + data_api_design = system_design.instruct_content.dict()[ + "Data structures and interface definitions" + ] # CodeParser.parse_code(block="Data structures and interface definitions", text=content) + seq_flow = system_design.instruct_content.dict()[ + "Program call flow" + ] # CodeParser.parse_code(block="Program call flow", text=content) + mermaid_to_file(data_api_design, resources_path / "data_api_design") + mermaid_to_file(seq_flow, resources_path / "seq_flow") + system_design_file = docs_path / "system_design.md" logger.info(f"Saving System Designs to {system_design_file}") - system_design_file.write_text(content) + system_design_file.write_text(system_design.content) def _save(self, context, system_design): if isinstance(system_design, ActionOutput): - content = system_design.content - ws_name = CodeParser.parse_str(block="Python package name", text=content) + ws_name = system_design.instruct_content.dict()["Python package name"] else: - content = system_design ws_name = CodeParser.parse_str(block="Python package name", text=system_design) workspace = WORKSPACE_ROOT / ws_name self.recreate_workspace(workspace) - docs_path = workspace / 'docs' - resources_path = workspace / 'resources' + docs_path = workspace / "docs" + resources_path = workspace / "resources" docs_path.mkdir(parents=True, exist_ok=True) resources_path.mkdir(parents=True, exist_ok=True) - self._save_prd(docs_path, resources_path, context[-1].content) - self._save_system_design(docs_path, resources_path, content) + self._save_prd(docs_path, resources_path, context) + self._save_system_design(docs_path, resources_path, system_design) async def run(self, context): prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) # system_design = await self._aask(prompt) - system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING) + system_design = await self._aask_json_v1(prompt, "system_design", OUTPUT_MAPPING) self._save(context, system_design) return system_design - \ No newline at end of file diff --git a/metagpt/actions/design_api_json.py b/metagpt/actions/design_api_json.py deleted file mode 100644 index fc20a37e0..000000000 --- a/metagpt/actions/design_api_json.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@Time : 2023/5/11 19:26 -@Author : alexanderwu -@File : design_api.py -""" -import shutil -from pathlib import Path -from typing import List - -from metagpt.actions import Action, ActionOutput -from metagpt.const import WORKSPACE_ROOT -from metagpt.logs import logger -from metagpt.utils.common import CodeParser -from metagpt.utils.mermaid import mermaid_to_file - -PROMPT_TEMPLATE = """ -# Context -{context} - -## Format example -{format_example} ------ -Role: You are an architect; the goal is to design a SOTA PEP8-compliant python system; make the best use of good open source tools -Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately -Max Output: 8192 chars or 2048 tokens. Try to use them up. - -## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework. - -## Python package name: Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores - -## File list: Provided as Python list[str], the list of ONLY REQUIRED files needed to write the program(LESS IS MORE!). Only need relative paths, comply with PEP8 standards. ALWAYS write a main.py or app.py here - -## Data structures and interface definitions: Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design. - -## Program call flow: Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT. - -## Anything UNCLEAR: Provide as Plain text. Make clear here. - -Your job is to create a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example -""" -FORMAT_EXAMPLE = """ -[CONTENT] -{ - "Implementation approach": "We will ...", - "Python package name": "snake_game", - "File list": ["main.py"], - "Data structures and interface definitions": ' - classDiagram - class Game{ - +int score - } - ... - Game "1" -- "1" Food: has - ', - "Program call flow": ' - sequenceDiagram - participant M as Main - ... - G->>M: end game - ', - "Anything UNCLEAR": "The requirement is clear to me." -} -[/CONTENT] -""" -OUTPUT_MAPPING = { - "Implementation approach": (str, ...), - "Python package name": (str, ...), - "File list": (List[str], ...), - "Data structures and interface definitions": (str, ...), - "Program call flow": (str, ...), - "Anything UNCLEAR": (str, ...), -} - - -class WriteDesignJson(Action): - def __init__(self, name, context=None, llm=None): - super().__init__(name, context, llm) - self.desc = "Based on the PRD, think about the system design, and design the corresponding APIs, " \ - "data structures, library tables, processes, and paths. Please provide your design, feedback " \ - "clearly and in detail." - - def recreate_workspace(self, workspace: Path): - try: - shutil.rmtree(workspace) - except FileNotFoundError: - pass # Folder does not exist, but we don't care - workspace.mkdir(parents=True, exist_ok=True) - - def _save_prd(self, docs_path, resources_path, context): - prd_file = docs_path / 'prd.md' - quadrant_chart = context[-1].instruct_content.dict()['Competitive Quadrant Chart'] - mermaid_to_file(quadrant_chart, resources_path / 'competitive_analysis') - logger.info(f"Saving PRD to {prd_file}") - prd_file.write_text(context[-1].content) - - def _save_system_design(self, docs_path, resources_path, system_design): - data_api_design = system_design.instruct_content.dict()['Data structures and interface definitions'] #CodeParser.parse_code(block="Data structures and interface definitions", text=content) - seq_flow = system_design.instruct_content.dict()['Program call flow'] #CodeParser.parse_code(block="Program call flow", text=content) - mermaid_to_file(data_api_design, resources_path / 'data_api_design') - mermaid_to_file(seq_flow, resources_path / 'seq_flow') - system_design_file = docs_path / 'system_design.md' - logger.info(f"Saving System Designs to {system_design_file}") - system_design_file.write_text(system_design.content) - - def _save(self, context, system_design): - if isinstance(system_design, ActionOutput): - content = system_design.content - ws_name = system_design.instruct_content.dict()['Python package name'] - else: - content = system_design - ws_name = CodeParser.parse_str(block="Python package name", text=system_design) - workspace = WORKSPACE_ROOT / ws_name - self.recreate_workspace(workspace) - docs_path = workspace / 'docs' - resources_path = workspace / 'resources' - docs_path.mkdir(parents=True, exist_ok=True) - resources_path.mkdir(parents=True, exist_ok=True) - self._save_prd(docs_path, resources_path, context) - self._save_system_design(docs_path, resources_path, system_design) - - async def run(self, context): - prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) - # system_design = await self._aask(prompt) - system_design = await self._aask_json_v1(prompt, "system_design", OUTPUT_MAPPING) - self._save(context, system_design) - return system_design - \ No newline at end of file diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index b69009645..7b531b4d3 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -9,9 +9,8 @@ from typing import List, Tuple from metagpt.actions.action import Action from metagpt.const import WORKSPACE_ROOT -from metagpt.utils.common import CodeParser -PROMPT_TEMPLATE = ''' +PROMPT_TEMPLATE = """ # Context {context} @@ -36,7 +35,7 @@ Attention: Use '##' to split sections, not '#', and '## ' SHOULD W ## Anything UNCLEAR: Provide as Plain text. Make clear here. For example, don't forget a main entry. don't forget to init 3rd party libs. -''' +""" FORMAT_EXAMPLE = ''' --- @@ -102,18 +101,21 @@ OUTPUT_MAPPING = { class WriteTasks(Action): - def __init__(self, name="CreateTasks", context=None, llm=None): super().__init__(name, context, llm) def _save(self, context, rsp): - ws_name = context[-1].instruct_content.dict()["Python package name"]#CodeParser.parse_str(block="Python package name", text=context[-1].content) - file_path = WORKSPACE_ROOT / ws_name / 'docs/api_spec_and_tasks.md' + ws_name = context[-1].instruct_content.dict()[ + "Python package name" + ] # CodeParser.parse_str(block="Python package name", text=context[-1].content) + file_path = WORKSPACE_ROOT / ws_name / "docs/api_spec_and_tasks.md" file_path.write_text(rsp.content) # Write requirements.txt - requirements_path = WORKSPACE_ROOT / ws_name / 'requirements.txt' - requirements_path.write_text(rsp.instruct_content.dict().get("Required Python third-party packages").strip('"\n')) + requirements_path = WORKSPACE_ROOT / ws_name / "requirements.txt" + requirements_path.write_text( + rsp.instruct_content.dict().get("Required Python third-party packages").strip('"\n') + ) async def run(self, context): prompt = PROMPT_TEMPLATE.format(context=context, format_example=FORMAT_EXAMPLE) @@ -126,4 +128,3 @@ class AssignTasks(Action): async def run(self, *args, **kwargs): # Here you should implement the actual action pass - \ No newline at end of file diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 2b96f867c..d2bb11eb8 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -5,7 +5,7 @@ @Author : alexanderwu @File : write_prd.py """ -from typing import List, Tuple +from typing import List from metagpt.actions import Action, ActionOutput from metagpt.actions.search_and_summarize import SearchAndSummarize @@ -43,7 +43,6 @@ quadrantChart ----- Role: You are a professional product manager; the goal is to design a concise, usable, efficient product Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly. If the requirements are unclear, ensure minimum viability and avoid excessive design -ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## ' SHOULD WRITE BEFORE the code and triple quote. Output carefully referenced "Format example" in format. ## Original Requirements: Provide as Plain text, place the polished complete original requirements here @@ -61,57 +60,45 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## ' SHOULD W ## UI Design draft: Provide as Plain text. Be simple. Describe the elements and functions, also provide a simple style description and layout description. ## Anything UNCLEAR: Provide as Plain text. Make clear here. + +Your job is to create a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example,output CONTENT json directly """ FORMAT_EXAMPLE = """ ---- -## Original Requirements -The boss ... +[CONTENT] +{ + "Original Requirements": "", + "Search Information": "", + "mermaid quadrantChart code": ' + 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] + ' + , -## Product Goals -```python -[ - "Create a ...", -] -``` - -## User Stories -```python -[ - "As a user, ...", -] -``` - -## Competitive Analysis -```python -[ - "Python Snake Game: ...", -] -``` - -## Competitive Quadrant Chart -```mermaid -quadrantChart - title Reach and engagement of campaigns - ... - "Our Target Product": [0.6, 0.7] -``` - -## Requirement Analysis -The product should be a ... - -## Requirement Pool -```python -[ - ("End game ...", "P0") -] -``` - -## UI Design draft -Give a basic function description, and a draft - -## Anything UNCLEAR -There are no unclear points. ---- + }, + "Role": "You are a professional product manager; the goal is to design a concise, usable, efficient product", + "Requirements": "", + "Product Goals": [], + "User Stories": [], + "Competitive Analysis": [], + "Competitive Quadrant Chart": "", + "Requirement Analysis": "", + "Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]], + "UI Design draft": "", + "Anything UNCLEAR": "", +} +[/CONTENT] """ OUTPUT_MAPPING = { "Original Requirements": (str, ...), @@ -120,8 +107,8 @@ OUTPUT_MAPPING = { "Competitive Analysis": (List[str], ...), "Competitive Quadrant Chart": (str, ...), "Requirement Analysis": (str, ...), - "Requirement Pool": (List[Tuple[str, str]], ...), - "UI Design draft":(str, ...), + "Requirement Pool": (List[List[str]], ...), + "UI Design draft": (str, ...), "Anything UNCLEAR": (str, ...), } @@ -139,9 +126,10 @@ class WritePRD(Action): logger.info(sas.result) logger.info(rsp) - prompt = PROMPT_TEMPLATE.format(requirements=requirements, search_information=info, - format_example=FORMAT_EXAMPLE) + prompt = PROMPT_TEMPLATE.format( + requirements=requirements, search_information=info, format_example=FORMAT_EXAMPLE + ) logger.debug(prompt) - prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING) + # prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING) + prd = await self._aask_json_v1(prompt, "prd", OUTPUT_MAPPING) return prd - \ No newline at end of file diff --git a/metagpt/actions/write_prd_json.py b/metagpt/actions/write_prd_json.py deleted file mode 100644 index adfac6f8a..000000000 --- a/metagpt/actions/write_prd_json.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@Time : 2023/5/11 17:45 -@Author : alexanderwu -@File : write_prd.py -""" -from typing import List, Tuple - -from metagpt.actions import Action, ActionOutput -from metagpt.actions.search_and_summarize import SearchAndSummarize -from metagpt.logs import logger - -PROMPT_TEMPLATE = """ -# Context -## Original Requirements -{requirements} - -## Search Information -{search_information} - -## mermaid quadrantChart code syntax example. DONT USE QUOTO IN CODE DUE TO INVALID SYNTAX. Replace the with REAL COMPETITOR NAME -```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] -``` - -## Format example -{format_example} ------ -Role: You are a professional product manager; the goal is to design a concise, usable, efficient product -Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly. If the requirements are unclear, ensure minimum viability and avoid excessive design - -## Original Requirements: Provide as Plain text, place the polished complete original requirements here - -## Product Goals: Provided as Python list[str], up to 3 clear, orthogonal product goals. If the requirement itself is simple, the goal should also be simple - -## User Stories: Provided as Python list[str], up to 5 scenario-based user stories, If the requirement itself is simple, the user stories should also be less - -## Competitive Analysis: Provided as Python list[str], up to 7 competitive product analyses, consider as similar competitors as possible - -## Competitive Quadrant Chart: Use mermaid quadrantChart code syntax. up to 14 competitive products. Translation: Distribute these competitor scores evenly between 0 and 1, trying to conform to a normal distribution centered around 0.5 as much as possible. - -## Requirement Analysis: Provide as Plain text. Be simple. LESS IS MORE. Make your requirements less dumb. Delete the parts unnessasery. - -## Requirement Pool: Provided as Python list[str, str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards; no more than 5 requirements and consider to make its difficulty lower - -## UI Design draft: Provide as Plain text. Be simple. Describe the elements and functions, also provide a simple style description and layout description. -## Anything UNCLEAR: Provide as Plain text. Make clear here. - -Your job is to create a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example,output CONTENT json directly -""" -FORMAT_EXAMPLE = """ -[CONTENT] -{ - "Original Requirements": "", - "Search Information": "", - "mermaid quadrantChart code": ' - 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] - ' - , - - }, - "Role": "You are a professional product manager; the goal is to design a concise, usable, efficient product", - "Requirements": "", - "Product Goals": [], - "User Stories": [], - "Competitive Analysis": [], - "Competitive Quadrant Chart": "", - "Requirement Analysis": "", - "Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]], - "UI Design draft": "", - "Anything UNCLEAR": "", -} -[/CONTENT] -""" -OUTPUT_MAPPING = { - "Original Requirements": (str, ...), - "Product Goals": (List[str], ...), - "User Stories": (List[str], ...), - "Competitive Analysis": (List[str], ...), - "Competitive Quadrant Chart": (str, ...), - "Requirement Analysis": (str, ...), - "Requirement Pool": (List[List[str]], ...), - "UI Design draft":(str, ...), - "Anything UNCLEAR": (str, ...), -} - - -class WritePRDJson(Action): - def __init__(self, name="", context=None, llm=None): - super().__init__(name, context, llm) - - async def run(self, requirements, *args, **kwargs) -> ActionOutput: - sas = SearchAndSummarize() - # rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US) - rsp = "" - info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}" - if sas.result: - logger.info(sas.result) - logger.info(rsp) - - prompt = PROMPT_TEMPLATE.format(requirements=requirements, search_information=info, - format_example=FORMAT_EXAMPLE) - logger.debug(prompt) - #prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING) - prd = await self._aask_json_v1(prompt, "prd", OUTPUT_MAPPING) - return prd - \ No newline at end of file diff --git a/metagpt/document_store/lancedb_store.py b/metagpt/document_store/lancedb_store.py index b366fa650..99c4575a6 100644 --- a/metagpt/document_store/lancedb_store.py +++ b/metagpt/document_store/lancedb_store.py @@ -5,13 +5,15 @@ @Author : unkn-wn (Leon Yee) @File : lancedb_store.py """ +import os +import shutil + import lancedb -import shutil, os class LanceStore: def __init__(self, name): - db = lancedb.connect('./data/lancedb') + db = lancedb.connect("./data/lancedb") self.db = db self.name = name self.table = None @@ -23,16 +25,18 @@ class LanceStore: # .where - SQL syntax filtering for metadata (e.g. where("price > 100")) # .metric - specifies the distance metric to use # .nprobes - values will yield better recall (more likely to find vectors if they exist) at the expense of latency. - if self.table == None: raise Exception("Table not created yet, please add data first.") + if self.table is None: + raise Exception("Table not created yet, please add data first.") - results = self.table \ - .search(query) \ - .limit(n_results) \ - .select(kwargs.get('select')) \ - .where(kwargs.get('where')) \ - .metric(metric) \ - .nprobes(nprobes) \ + results = ( + self.table.search(query) + .limit(n_results) + .select(kwargs.get("select")) + .where(kwargs.get("where")) + .metric(metric) + .nprobes(nprobes) .to_df() + ) return results def persist(self): @@ -45,14 +49,11 @@ class LanceStore: documents = [] for i in range(len(data)): - row = { - 'vector': data[i], - 'id': ids[i] - } + row = {"vector": data[i], "id": ids[i]} row.update(metadatas[i]) documents.append(row) - if self.table != None: + if self.table is not None: self.table.add(documents) else: self.table = self.db.create_table(self.name, documents) @@ -61,13 +62,10 @@ class LanceStore: # This function is for adding individual documents # It assumes you're passing in a single vector embedding, metadata, and id - row = { - 'vector': data, - 'id': _id - } + row = {"vector": data, "id": _id} row.update(metadata) - if self.table != None: + if self.table is not None: self.table.add([row]) else: self.table = self.db.create_table(self.name, [row]) @@ -75,7 +73,8 @@ class LanceStore: def delete(self, _id): # This function deletes a row by id. # LanceDB delete syntax uses SQL syntax, so you can use "in" or "=" - if self.table == None: raise Exception("Table not created yet, please add data first") + if self.table is None: + raise Exception("Table not created yet, please add data first") if isinstance(_id, str): return self.table.delete(f"id = '{_id}'") @@ -85,6 +84,6 @@ class LanceStore: def drop(self, name): # This function drops a table, if it exists. - path = os.path.join(self.db.uri, name + '.lance') + path = os.path.join(self.db.uri, name + ".lance") if os.path.exists(path): - shutil.rmtree(path) \ No newline at end of file + shutil.rmtree(path) diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index bd52665dc..15d5fe5b1 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -6,34 +6,34 @@ @File : architect.py """ -from metagpt.actions import WriteDesign, WritePRD, WritePRDJson -from metagpt.actions.design_api_json import WriteDesignJson +from metagpt.actions import WritePRD +from metagpt.actions.design_api import WriteDesign from metagpt.roles import Role class Architect(Role): """ Represents an Architect role in a software development process. - + Attributes: name (str): Name of the architect. profile (str): Role profile, default is 'Architect'. goal (str): Primary goal or responsibility of the architect. constraints (str): Constraints or guidelines for the architect. """ - - def __init__(self, - name: str = "Bob", - profile: str = "Architect", - goal: str = "Design a concise, usable, complete python system", - constraints: str = "Try to specify good open source tools as much as possible") -> None: + + def __init__( + self, + name: str = "Bob", + profile: str = "Architect", + goal: str = "Design a concise, usable, complete python system", + constraints: str = "Try to specify good open source tools as much as possible", + ) -> None: """Initializes the Architect with given attributes.""" super().__init__(name, profile, goal, constraints) - - # Initialize actions specific to the Architect role - self._init_actions([WriteDesignJson]) - - # Set events or actions the Architect should watch or be aware of - self._watch({WritePRDJson}) - \ No newline at end of file + # Initialize actions specific to the Architect role + self._init_actions([WriteDesign]) + + # Set events or actions the Architect should watch or be aware of + self._watch({WritePRD}) diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index c00b084ee..6d65575a8 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -10,14 +10,13 @@ import shutil from collections import OrderedDict from pathlib import Path -from metagpt.actions.design_api_json import WriteDesignJson +from metagpt.actions import WriteCode, WriteCodeReview, WriteDesign, WriteTasks from metagpt.const import WORKSPACE_ROOT from metagpt.logs import logger from metagpt.roles import Role -from metagpt.actions import WriteCode, WriteCodeReview, WriteTasks, WriteDesign from metagpt.schema import Message from metagpt.utils.common import CodeParser -from metagpt.utils.special_tokens import MSG_SEP, FILENAME_CODE_SEP +from metagpt.utils.special_tokens import FILENAME_CODE_SEP, MSG_SEP async def gather_ordered_k(coros, k) -> list: @@ -50,7 +49,7 @@ async def gather_ordered_k(coros, k) -> list: class Engineer(Role): """ Represents an Engineer role responsible for writing and possibly reviewing code. - + Attributes: name (str): Name of the engineer. profile (str): Role profile, default is 'Engineer'. @@ -60,14 +59,16 @@ class Engineer(Role): use_code_review (bool): Whether to use code review. todos (list): List of tasks. """ - - def __init__(self, - name: str = "Alex", - profile: str = "Engineer", - goal: str = "Write elegant, readable, extensible, efficient code", - constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable", - n_borg: int = 1, - use_code_review: bool = False) -> None: + + def __init__( + self, + name: str = "Alex", + profile: str = "Engineer", + goal: str = "Write elegant, readable, extensible, efficient code", + constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable", + n_borg: int = 1, + use_code_review: bool = False, + ) -> None: """Initializes the Engineer role with given attributes.""" super().__init__(name, profile, goal, constraints) self._init_actions([WriteCode]) @@ -91,13 +92,13 @@ class Engineer(Role): @classmethod def parse_workspace(cls, system_design_msg: Message) -> str: if system_design_msg.instruct_content: - return system_design_msg.instruct_content.dict().get("Python package name").strip().strip("'").strip("\"") + return system_design_msg.instruct_content.dict().get("Python package name").strip().strip("'").strip('"') return CodeParser.parse_str(block="Python package name", text=system_design_msg.content) def get_workspace(self) -> Path: - msg = self._rc.memory.get_by_action(WriteDesignJson)[-1] + msg = self._rc.memory.get_by_action(WriteDesign)[-1] if not msg: - return WORKSPACE_ROOT / 'src' + return WORKSPACE_ROOT / "src" workspace = self.parse_workspace(msg) # Codes are written in workspace/{package_name}/{package_name} return WORKSPACE_ROOT / workspace / workspace @@ -112,7 +113,7 @@ class Engineer(Role): def write_file(self, filename: str, code: str): workspace = self.get_workspace() - filename = filename.replace('"', '').replace('\n', '') + filename = filename.replace('"', "").replace("\n", "") file = workspace / filename file.parent.mkdir(parents=True, exist_ok=True) file.write_text(code) @@ -128,8 +129,7 @@ class Engineer(Role): todo_coros = [] for todo in self.todos: todo_coro = WriteCode().run( - context=self._rc.memory.get_by_actions([WriteTasks, WriteDesign]), - filename=todo + context=self._rc.memory.get_by_actions([WriteTasks, WriteDesign]), filename=todo ) todo_coros.append(todo_coro) @@ -143,17 +143,14 @@ class Engineer(Role): self._rc.memory.add(msg) del self.todos[0] - logger.info(f'Done {self.get_workspace()} generating.') + logger.info(f"Done {self.get_workspace()} generating.") msg = Message(content="all done.", role=self.profile, cause_by=type(self._rc.todo)) return msg async def _act_sp(self) -> Message: - code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later + code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later for todo in self.todos: - code = await WriteCode().run( - context=self._rc.history, - filename=todo - ) + code = await WriteCode().run(context=self._rc.history, filename=todo) # logger.info(todo) # logger.info(code_rsp) # code = self.parse_code(code_rsp) @@ -164,17 +161,14 @@ class Engineer(Role): code_msg = todo + FILENAME_CODE_SEP + str(file_path) code_msg_all.append(code_msg) - logger.info(f'Done {self.get_workspace()} generating.') + logger.info(f"Done {self.get_workspace()} generating.") msg = Message( - content=MSG_SEP.join(code_msg_all), - role=self.profile, - cause_by=type(self._rc.todo), - send_to="QaEngineer" + content=MSG_SEP.join(code_msg_all), role=self.profile, cause_by=type(self._rc.todo), send_to="QaEngineer" ) return msg async def _act_sp_precision(self) -> Message: - code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later + code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later for todo in self.todos: """ # Select essential information from the historical data to reduce the length of the prompt (summarized from human experience): @@ -189,18 +183,11 @@ class Engineer(Role): context.append(m.content) context_str = "\n".join(context) # Write code - code = await WriteCode().run( - context=context_str, - filename=todo - ) + code = await WriteCode().run(context=context_str, filename=todo) # Code review if self.use_code_review: try: - rewrite_code = await WriteCodeReview().run( - context=context_str, - code=code, - filename=todo - ) + rewrite_code = await WriteCodeReview().run(context=context_str, code=code, filename=todo) code = rewrite_code except Exception as e: logger.error("code review failed!", e) @@ -212,12 +199,9 @@ class Engineer(Role): code_msg = todo + FILENAME_CODE_SEP + str(file_path) code_msg_all.append(code_msg) - logger.info(f'Done {self.get_workspace()} generating.') + logger.info(f"Done {self.get_workspace()} generating.") msg = Message( - content=MSG_SEP.join(code_msg_all), - role=self.profile, - cause_by=type(self._rc.todo), - send_to="QaEngineer" + content=MSG_SEP.join(code_msg_all), role=self.profile, cause_by=type(self._rc.todo), send_to="QaEngineer" ) return msg @@ -225,4 +209,4 @@ class Engineer(Role): """Determines the mode of action based on whether code review is used.""" if self.use_code_review: return await self._act_sp_precision() - return await self._act_sp() \ No newline at end of file + return await self._act_sp() diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index fefcbfe34..a58ea5385 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -5,29 +5,31 @@ @Author : alexanderwu @File : product_manager.py """ -from metagpt.actions import BossRequirement, WritePRD,WritePRDJson +from metagpt.actions import BossRequirement, WritePRD from metagpt.roles import Role class ProductManager(Role): """ Represents a Product Manager role responsible for product development and management. - + Attributes: name (str): Name of the product manager. profile (str): Role profile, default is 'Product Manager'. goal (str): Goal of the product manager. constraints (str): Constraints or limitations for the product manager. """ - - def __init__(self, - name: str = "Alice", - profile: str = "Product Manager", - goal: str = "Efficiently create a successful product", - constraints: str = "") -> None: + + def __init__( + self, + name: str = "Alice", + profile: str = "Product Manager", + goal: str = "Efficiently create a successful product", + constraints: str = "", + ) -> None: """ Initializes the ProductManager role with given attributes. - + Args: name (str): Name of the product manager. profile (str): Role profile. @@ -35,5 +37,5 @@ class ProductManager(Role): constraints (str): Constraints or limitations for the product manager. """ super().__init__(name, profile, goal, constraints) - self._init_actions([WritePRDJson]) - self._watch([BossRequirement]) \ No newline at end of file + self._init_actions([WritePRD]) + self._watch([BossRequirement]) diff --git a/metagpt/roles/project_manager.py b/metagpt/roles/project_manager.py index 78d2f8d52..7e7c5699d 100644 --- a/metagpt/roles/project_manager.py +++ b/metagpt/roles/project_manager.py @@ -5,30 +5,32 @@ @Author : alexanderwu @File : project_manager.py """ -from metagpt.actions import WriteDesign, WriteTasks -from metagpt.actions.design_api_json import WriteDesignJson +from metagpt.actions import WriteTasks +from metagpt.actions.design_api import WriteDesign from metagpt.roles import Role class ProjectManager(Role): """ Represents a Project Manager role responsible for overseeing project execution and team efficiency. - + Attributes: name (str): Name of the project manager. profile (str): Role profile, default is 'Project Manager'. goal (str): Goal of the project manager. constraints (str): Constraints or limitations for the project manager. """ - - def __init__(self, - name: str = "Eve", - profile: str = "Project Manager", - goal: str = "Improve team efficiency and deliver with quality and quantity", - constraints: str = "") -> None: + + def __init__( + self, + name: str = "Eve", + profile: str = "Project Manager", + goal: str = "Improve team efficiency and deliver with quality and quantity", + constraints: str = "", + ) -> None: """ Initializes the ProjectManager role with given attributes. - + Args: name (str): Name of the project manager. profile (str): Role profile. @@ -37,4 +39,4 @@ class ProjectManager(Role): """ super().__init__(name, profile, goal, constraints) self._init_actions([WriteTasks]) - self._watch([WriteDesignJson]) \ No newline at end of file + self._watch([WriteDesign]) diff --git a/metagpt/utils/custom_decoder.py b/metagpt/utils/custom_decoder.py index 4c4aaa587..02a8ab41c 100644 --- a/metagpt/utils/custom_decoder.py +++ b/metagpt/utils/custom_decoder.py @@ -1,10 +1,11 @@ import json import re -from json import scanner, JSONDecodeError +from json import JSONDecodeError from json.decoder import _decode_uXXXX -NUMBER_RE = re.compile( - r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', - (re.VERBOSE | re.MULTILINE | re.DOTALL)) + +NUMBER_RE = re.compile(r"(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?", (re.VERBOSE | re.MULTILINE | re.DOTALL)) + + def py_make_scanner(context): parse_object = context.parse_object parse_array = context.parse_array @@ -26,32 +27,31 @@ def py_make_scanner(context): if nextchar == '"' or nextchar == "'": return parse_string(string, idx + 1, strict, delimiter=nextchar) - elif nextchar == '{': - return parse_object((string, idx + 1), strict, - _scan_once, object_hook, object_pairs_hook, memo) - elif nextchar == '[': + elif nextchar == "{": + return parse_object((string, idx + 1), strict, _scan_once, object_hook, object_pairs_hook, memo) + elif nextchar == "[": return parse_array((string, idx + 1), _scan_once) - elif nextchar == 'n' and string[idx:idx + 4] == 'null': + elif nextchar == "n" and string[idx : idx + 4] == "null": return None, idx + 4 - elif nextchar == 't' and string[idx:idx + 4] == 'true': + elif nextchar == "t" and string[idx : idx + 4] == "true": return True, idx + 4 - elif nextchar == 'f' and string[idx:idx + 5] == 'false': + elif nextchar == "f" and string[idx : idx + 5] == "false": return False, idx + 5 m = match_number(string, idx) if m is not None: integer, frac, exp = m.groups() if frac or exp: - res = parse_float(integer + (frac or '') + (exp or '')) + res = parse_float(integer + (frac or "") + (exp or "")) else: res = parse_int(integer) return res, m.end() - elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': - return parse_constant('NaN'), idx + 3 - elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': - return parse_constant('Infinity'), idx + 8 - elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': - return parse_constant('-Infinity'), idx + 9 + elif nextchar == "N" and string[idx : idx + 3] == "NaN": + return parse_constant("NaN"), idx + 3 + elif nextchar == "I" and string[idx : idx + 8] == "Infinity": + return parse_constant("Infinity"), idx + 8 + elif nextchar == "-" and string[idx : idx + 9] == "-Infinity": + return parse_constant("-Infinity"), idx + 9 else: raise StopIteration(idx) @@ -62,17 +62,28 @@ def py_make_scanner(context): memo.clear() return scan_once + + FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) -STRINGCHUNK_SINGLEQUOTE = re.compile(r'(.*?)([\'\\\x00-\x1f])', FLAGS) +STRINGCHUNK_SINGLEQUOTE = re.compile(r"(.*?)([\'\\\x00-\x1f])", FLAGS) BACKSLASH = { - '"': '"', '\\': '\\', '/': '/', - 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', + '"': '"', + "\\": "\\", + "/": "/", + "b": "\b", + "f": "\f", + "n": "\n", + "r": "\r", + "t": "\t", } -WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) -WHITESPACE_STR = ' \t\n\r' -def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, - memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR): +WHITESPACE = re.compile(r"[ \t\n\r]*", FLAGS) +WHITESPACE_STR = " \t\n\r" + + +def JSONObject( + s_and_end, strict, scan_once, object_hook, object_pairs_hook, memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR +): s, end = s_and_end pairs = [] pairs_append = pairs.append @@ -82,14 +93,14 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, memo_get = memo.setdefault # Use a slice to prevent IndexError from being raised, the following # check will raise a more specific ValueError if the string is empty - nextchar = s[end:end + 1] + nextchar = s[end : end + 1] # Normally we expect nextchar == '"' if nextchar != '"' and nextchar != "'": if nextchar in _ws: end = _w(s, end).end() - nextchar = s[end:end + 1] + nextchar = s[end : end + 1] # Trivial empty object - if nextchar == '}': + if nextchar == "}": if object_pairs_hook is not None: result = object_pairs_hook(pairs) return result, end + 1 @@ -98,17 +109,16 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, pairs = object_hook(pairs) return pairs, end + 1 elif nextchar != '"': - raise JSONDecodeError( - "Expecting property name enclosed in double quotes", s, end) + raise JSONDecodeError("Expecting property name enclosed in double quotes", s, end) end += 1 while True: key, end = scanstring(s, end, strict, delimiter=nextchar) key = memo_get(key, key) # To skip some function call overhead we optimize the fast paths where # the JSON key separator is ": " or just ":". - if s[end:end + 1] != ':': + if s[end : end + 1] != ":": end = _w(s, end).end() - if s[end:end + 1] != ':': + if s[end : end + 1] != ":": raise JSONDecodeError("Expecting ':' delimiter", s, end) end += 1 @@ -131,19 +141,18 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, end = _w(s, end + 1).end() nextchar = s[end] except IndexError: - nextchar = '' + nextchar = "" end += 1 - if nextchar == '}': + if nextchar == "}": break - elif nextchar != ',': + elif nextchar != ",": raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) end = _w(s, end).end() - nextchar = s[end:end + 1] + nextchar = s[end : end + 1] end += 1 if nextchar != '"': - raise JSONDecodeError( - "Expecting property name enclosed in double quotes", s, end - 1) + raise JSONDecodeError("Expecting property name enclosed in double quotes", s, end - 1) if object_pairs_hook is not None: result = object_pairs_hook(pairs) return result, end @@ -151,8 +160,9 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, if object_hook is not None: pairs = object_hook(pairs) return pairs, end -def py_scanstring(s, end, strict=True, - _b=BACKSLASH, _m=STRINGCHUNK.match,delimiter='"'): + + +def py_scanstring(s, end, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match, delimiter='"'): """Scan the string s for a JSON string. End is the index of the character in s after the quote that started the JSON string. Unescapes all valid JSON string escape sequences and raises ValueError @@ -181,9 +191,9 @@ def py_scanstring(s, end, strict=True, # or a backslash denoting that an escape sequence follows if terminator == delimiter: break - elif terminator != '\\': + elif terminator != "\\": if strict: - #msg = "Invalid control character %r at" % (terminator,) + # msg = "Invalid control character %r at" % (terminator,) msg = "Invalid control character {0!r} at".format(terminator) raise JSONDecodeError(msg, s, end) else: @@ -192,10 +202,9 @@ def py_scanstring(s, end, strict=True, try: esc = s[end] except IndexError: - raise JSONDecodeError("Unterminated string starting at", - s, begin) from None + raise JSONDecodeError("Unterminated string starting at", s, begin) from None # If not a unicode escape sequence, must be in the lookup table - if esc != 'u': + if esc != "u": try: char = _b[esc] except KeyError: @@ -205,26 +214,41 @@ def py_scanstring(s, end, strict=True, else: uni = _decode_uXXXX(s, end) end += 5 - if 0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u': + if 0xD800 <= uni <= 0xDBFF and s[end : end + 2] == "\\u": uni2 = _decode_uXXXX(s, end + 1) - if 0xdc00 <= uni2 <= 0xdfff: - uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + if 0xDC00 <= uni2 <= 0xDFFF: + uni = 0x10000 + (((uni - 0xD800) << 10) | (uni2 - 0xDC00)) end += 6 char = chr(uni) _append(char) - return ''.join(chunks), end + return "".join(chunks), end + + scanstring = py_scanstring + + class CustomDecoder(json.JSONDecoder): - def __init__(self, *, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, strict=True, - object_pairs_hook=None): - super().__init__(object_hook=object_hook, parse_float=parse_float, parse_int=parse_int, - parse_constant=parse_constant, strict=strict,object_pairs_hook=object_pairs_hook) + def __init__( + self, + *, + object_hook=None, + parse_float=None, + parse_int=None, + parse_constant=None, + strict=True, + object_pairs_hook=None + ): + super().__init__( + object_hook=object_hook, + parse_float=parse_float, + parse_int=parse_int, + parse_constant=parse_constant, + strict=strict, + object_pairs_hook=object_pairs_hook, + ) self.parse_object = JSONObject self.parse_string = py_scanstring self.scan_once = py_make_scanner(self) def decode(self, s, _w=json.decoder.WHITESPACE.match): - return super().decode(s) - diff --git a/tests/metagpt/utils/test_custom_decoder.py b/tests/metagpt/utils/test_custom_decoder.py index 754375549..99ca3fc6a 100644 --- a/tests/metagpt/utils/test_custom_decoder.py +++ b/tests/metagpt/utils/test_custom_decoder.py @@ -5,17 +5,16 @@ @Author : femto Zheng @File : test_custom_decoder.py """ -import pytest -import json from metagpt.utils.custom_decoder import CustomDecoder + def test_parse_single_quote(): # Create a custom JSON decoder decoder = CustomDecoder(strict=False) # Your provided input with single-quoted strings and line breaks - input_data = '''{'a" + input_data = """{'a" b':'"title": "Reach and engagement of campaigns", "x-axis": "Low Reach --> High Reach", "y-axis": "Low Engagement --> High Engagement", @@ -32,9 +31,8 @@ def test_parse_single_quote(): "Our Target Product": [0.5, 0.6] ' } - ''' + """ # Parse the JSON using the custom decoder parsed_data = decoder.decode(input_data) assert 'a"\n b' in parsed_data -