From f441c88156404a87ab4f31571ca5e1ab5497b86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sun, 24 Dec 2023 12:30:08 +0800 Subject: [PATCH 1/2] fixbug: timeout & prompt_format --- config/config.yaml | 1 + metagpt/actions/action_node.py | 15 +++++++++------ metagpt/actions/design_api.py | 6 +++--- metagpt/actions/project_management.py | 6 +++--- metagpt/actions/write_prd.py | 6 +++--- metagpt/config.py | 10 ++++++++-- metagpt/provider/azure_openai_api.py | 6 +----- metagpt/provider/ollama_api.py | 3 ++- metagpt/provider/openai_api.py | 8 ++------ metagpt/roles/engineer.py | 9 +++++---- metagpt/roles/product_manager.py | 12 ++++++------ metagpt/roles/role.py | 5 ----- metagpt/tools/web_browser_engine.py | 3 +-- metagpt/utils/get_template.py | 2 +- requirements.txt | 1 + 15 files changed, 46 insertions(+), 47 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index 6d3095717..ab4d49f5d 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -15,6 +15,7 @@ OPENAI_API_MODEL: "gpt-4-1106-preview" MAX_TOKENS: 4096 RPM: 10 LLM_TYPE: OpenAI # Except for these three major models – OpenAI, MetaGPT LLM, and Azure – other large models can be distinguished based on the validity of the key. +TIMEOUT: 60 # Timeout for llm invocation #### if Spark #SPARK_APPID : "YOUR_APPID" diff --git a/metagpt/actions/action_node.py b/metagpt/actions/action_node.py index 3529942c3..63f46ad45 100644 --- a/metagpt/actions/action_node.py +++ b/metagpt/actions/action_node.py @@ -14,6 +14,7 @@ from pydantic import BaseModel, create_model, root_validator, validator from tenacity import retry, stop_after_attempt, wait_random_exponential +from metagpt.config import CONFIG from metagpt.llm import BaseGPTAPI from metagpt.logs import logger from metagpt.provider.postprecess.llm_output_postprecess import llm_output_postprecess @@ -260,9 +261,10 @@ async def _aask_v1( output_data_mapping: dict, system_msgs: Optional[list[str]] = None, schema="markdown", # compatible to original format + timeout=CONFIG.timeout, ) -> (str, BaseModel): """Use ActionOutput to wrap the output of aask""" - content = await self.llm.aask(prompt, system_msgs) + content = await self.llm.aask(prompt, system_msgs, timeout=timeout) logger.debug(f"llm raw output:\n{content}") output_class = self.create_model_class(output_class_name, output_data_mapping) @@ -289,13 +291,13 @@ def set_llm(self, llm): def set_context(self, context): self.set_recursive("context", context) - async def simple_fill(self, schema, mode): + async def simple_fill(self, schema, mode, timeout=CONFIG.timeout): prompt = self.compile(context=self.context, schema=schema, mode=mode) if schema != "raw": mapping = self.get_mapping(mode) class_name = f"{self.key}_AN" - content, scontent = await self._aask_v1(prompt, class_name, mapping, schema=schema) + content, scontent = await self._aask_v1(prompt, class_name, mapping, schema=schema, timeout=timeout) self.content = content self.instruct_content = scontent else: @@ -304,7 +306,7 @@ async def simple_fill(self, schema, mode): return self - async def fill(self, context, llm, schema="json", mode="auto", strgy="simple"): + async def fill(self, context, llm, schema="json", mode="auto", strgy="simple", timeout=CONFIG.timeout): """Fill the node(s) with mode. :param context: Everything we should know when filling node. @@ -320,6 +322,7 @@ async def fill(self, context, llm, schema="json", mode="auto", strgy="simple"): :param strgy: simple/complex - simple: run only once - complex: run each node + :param timeout: Timeout for llm invocation. :return: self """ self.set_llm(llm) @@ -328,12 +331,12 @@ async def fill(self, context, llm, schema="json", mode="auto", strgy="simple"): schema = self.schema if strgy == "simple": - return await self.simple_fill(schema=schema, mode=mode) + return await self.simple_fill(schema=schema, mode=mode, timeout=timeout) elif strgy == "complex": # 这里隐式假设了拥有children tmp = {} for _, i in self.children.items(): - child = await i.simple_fill(schema=schema, mode=mode) + child = await i.simple_fill(schema=schema, mode=mode, timeout=timeout) tmp.update(child.instruct_content.dict()) cls = self.create_children_class() self.instruct_content = cls(**tmp) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 055365421..e23fcdb2e 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -51,7 +51,7 @@ class WriteDesign(Action): "clearly and in detail." ) - async def run(self, with_messages: Message, schema: str = CONFIG.prompt_schema): + async def run(self, with_messages: Message, schema: str = CONFIG.prompt_format): # Use `git diff` to identify which PRD documents have been modified in the `docs/prds` directory. prds_file_repo = CONFIG.git_repo.new_file_repository(PRDS_FILE_REPO) changed_prds = prds_file_repo.changed_files @@ -81,11 +81,11 @@ async def run(self, with_messages: Message, schema: str = CONFIG.prompt_schema): # leaving room for global optimization in subsequent steps. return ActionOutput(content=changed_files.json(), instruct_content=changed_files) - async def _new_system_design(self, context, schema=CONFIG.prompt_schema): + async def _new_system_design(self, context, schema=CONFIG.prompt_format): node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=schema) return node - async def _merge(self, prd_doc, system_design_doc, schema=CONFIG.prompt_schema): + async def _merge(self, prd_doc, system_design_doc, schema=CONFIG.prompt_format): context = NEW_REQ_TEMPLATE.format(old_design=system_design_doc.content, context=prd_doc.content) node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=schema) system_design_doc.content = node.instruct_content.json(ensure_ascii=False) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 095881e60..3086c4d96 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -45,7 +45,7 @@ class WriteTasks(Action): context: Optional[str] = None llm: BaseGPTAPI = Field(default_factory=LLM) - async def run(self, with_messages, schema=CONFIG.prompt_schema): + async def run(self, with_messages, schema=CONFIG.prompt_format): system_design_file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO) changed_system_designs = system_design_file_repo.changed_files @@ -92,14 +92,14 @@ async def _update_tasks(self, filename, system_design_file_repo, tasks_file_repo await self._save_pdf(task_doc=task_doc) return task_doc - async def _run_new_tasks(self, context, schema=CONFIG.prompt_schema): + async def _run_new_tasks(self, context, schema=CONFIG.prompt_format): node = await PM_NODE.fill(context, self.llm, schema) # prompt_template, format_example = get_template(templates, format) # prompt = prompt_template.format(context=context, format_example=format_example) # rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format) return node - async def _merge(self, system_design_doc, task_doc, schema=CONFIG.prompt_schema) -> Document: + async def _merge(self, system_design_doc, task_doc, schema=CONFIG.prompt_format) -> Document: context = NEW_REQ_TEMPLATE.format(context=system_design_doc.content, old_tasks=task_doc.content) node = await PM_NODE.fill(context, self.llm, schema) task_doc.content = node.instruct_content.json(ensure_ascii=False) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 1223e5486..362d4cc82 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -69,7 +69,7 @@ class WritePRD(Action): content: Optional[str] = None llm: BaseGPTAPI = Field(default_factory=LLM) - async def run(self, with_messages, schema=CONFIG.prompt_schema, *args, **kwargs) -> ActionOutput | Message: + async def run(self, with_messages, schema=CONFIG.prompt_format, *args, **kwargs) -> ActionOutput | Message: # Determine which requirement documents need to be rewritten: Use LLM to assess whether new requirements are # related to the PRD. If they are related, rewrite the PRD. docs_file_repo = CONFIG.git_repo.new_file_repository(relative_path=DOCS_FILE_REPO) @@ -113,7 +113,7 @@ async def run(self, with_messages, schema=CONFIG.prompt_schema, *args, **kwargs) # optimization in subsequent steps. return ActionOutput(content=change_files.json(), instruct_content=change_files) - async def _run_new_requirement(self, requirements, schema=CONFIG.prompt_schema) -> ActionOutput: + async def _run_new_requirement(self, requirements, schema=CONFIG.prompt_format) -> ActionOutput: # sas = SearchAndSummarize() # # rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US) # rsp = "" @@ -132,7 +132,7 @@ async def _is_relative(self, new_requirement_doc, old_prd_doc) -> bool: node = await WP_IS_RELATIVE_NODE.fill(context, self.llm) return node.get("is_relative") == "YES" - async def _merge(self, new_requirement_doc, prd_doc, schema=CONFIG.prompt_schema) -> Document: + async def _merge(self, new_requirement_doc, prd_doc, schema=CONFIG.prompt_format) -> Document: if not CONFIG.project_name: CONFIG.project_name = Path(CONFIG.project_path).name prompt = NEW_REQ_TEMPLATE.format(requirements=new_requirement_doc.content, old_prd=prd_doc.content) diff --git a/metagpt/config.py b/metagpt/config.py index a7bd191ab..45bdb9bdc 100644 --- a/metagpt/config.py +++ b/metagpt/config.py @@ -109,8 +109,13 @@ def get_default_llm_provider_enum(self) -> LLMProviderEnum: if provider is LLMProviderEnum.GEMINI and not require_python_version(req_version=(3, 10)): warnings.warn("Use Gemini requires Python >= 3.10") - if self.openai_api_key and self.openai_api_model: - logger.info(f"OpenAI API Model: {self.openai_api_model}") + model_mappings = { + LLMProviderEnum.OPENAI: self.OPENAI_API_MODEL, + LLMProviderEnum.AZURE_OPENAI: self.DEPLOYMENT_NAME, + } + model_name = model_mappings.get(provider) + if model_name: + logger.info(f"{provider} Model: {model_name}") if provider: logger.info(f"API: {provider}") return provider @@ -187,6 +192,7 @@ def _update(self): self.workspace_path = self.workspace_path / workspace_uid self._ensure_workspace_exists() self.max_auto_summarize_code = self.max_auto_summarize_code or self._get("MAX_AUTO_SUMMARIZE_CODE", 1) + self.timeout = int(self._get("TIMEOUT", 3)) def update_via_cli(self, project_path, project_name, inc, reqa_file, max_auto_summarize_code): """update config via cli""" diff --git a/metagpt/provider/azure_openai_api.py b/metagpt/provider/azure_openai_api.py index 7a2952d43..ca0696830 100644 --- a/metagpt/provider/azure_openai_api.py +++ b/metagpt/provider/azure_openai_api.py @@ -64,10 +64,6 @@ def _cons_kwargs(self, messages: list[dict], timeout=3, **configs) -> dict: } if configs: kwargs.update(configs) - try: - default_timeout = int(CONFIG.TIMEOUT) if CONFIG.TIMEOUT else 0 - except ValueError: - default_timeout = 0 - kwargs["timeout"] = max(default_timeout, timeout) + kwargs["timeout"] = max(CONFIG.timeout, timeout) return kwargs diff --git a/metagpt/provider/ollama_api.py b/metagpt/provider/ollama_api.py index a15c46458..05bdb5a1f 100644 --- a/metagpt/provider/ollama_api.py +++ b/metagpt/provider/ollama_api.py @@ -19,7 +19,8 @@ from metagpt.provider.base_gpt_api import BaseGPTAPI from metagpt.provider.general_api_requestor import GeneralAPIRequestor from metagpt.provider.llm_provider_registry import register_provider -from metagpt.provider.openai_api import CostManager, log_and_reraise +from metagpt.provider.openai_api import log_and_reraise +from metagpt.utils.cost_manager import CostManager class OllamaCostManager(CostManager): diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 1c292263f..9305052b8 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -129,7 +129,7 @@ async def _achat_completion_stream(self, messages: list[dict], timeout=3) -> str ) async for chunk in response: - chunk_message = chunk.choices[0].delta.content or "" # extract the message + chunk_message = chunk.choices[0].delta.content or "" if chunk.choices else "" # extract the message yield chunk_message def _cons_kwargs(self, messages: list[dict], timeout=3, **configs) -> dict: @@ -143,11 +143,7 @@ def _cons_kwargs(self, messages: list[dict], timeout=3, **configs) -> dict: } if configs: kwargs.update(configs) - try: - default_timeout = int(CONFIG.TIMEOUT) if CONFIG.TIMEOUT else 0 - except ValueError: - default_timeout = 0 - kwargs["timeout"] = max(default_timeout, timeout) + kwargs["timeout"] = max(CONFIG.timeout, timeout) return kwargs diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 12deaa5bb..994c176e9 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -80,6 +80,8 @@ class Engineer(Role): code_todos: list = [] summarize_todos = [] + todo_desc: str = any_to_name(WriteCode) + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) @@ -87,7 +89,6 @@ def __init__(self, **kwargs) -> None: self._watch([WriteTasks, SummarizeCode, WriteCode, WriteCodeReview, FixBug]) self.code_todos = [] self.summarize_todos = [] - self._next_todo = any_to_name(WriteCode) @staticmethod def _parse_tasks(task_msg: Document) -> list[str]: @@ -131,10 +132,10 @@ async def _act(self) -> Message | None: if self._rc.todo is None: return None if isinstance(self._rc.todo, WriteCode): - self._next_todo = any_to_name(SummarizeCode) + self.todo_desc = any_to_name(SummarizeCode) return await self._act_write_code() if isinstance(self._rc.todo, SummarizeCode): - self._next_todo = any_to_name(WriteCode) + self.todo_desc = any_to_name(WriteCode) return await self._act_summarize() return None @@ -310,4 +311,4 @@ async def _new_summarize_actions(self): @property def todo(self) -> str: """AgentStore uses this attribute to display to the user what actions the current role should take.""" - return self._next_todo + return self.todo_desc diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index 0f18c9cb2..847649a82 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -7,7 +7,6 @@ @Modified By: mashenquan, 2023/11/27. Add `PrepareDocuments` action according to Section 2.2.3.5.1 of RFC 135. """ - from metagpt.actions import UserRequirement, WritePRD from metagpt.actions.prepare_documents import PrepareDocuments from metagpt.config import CONFIG @@ -31,21 +30,22 @@ class ProductManager(Role): goal: str = "efficiently create a successful product that meets market demands and user expectations" constraints: str = "utilize the same language as the user requirements for seamless communication" + todo_desc: str = any_to_name(PrepareDocuments) + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self._init_actions([PrepareDocuments, WritePRD]) self._watch([UserRequirement, PrepareDocuments]) - self._todo = any_to_name(PrepareDocuments) - async def _think(self) -> None: + async def _think(self) -> bool: """Decide what to do""" if CONFIG.git_repo: self._set_state(1) else: self._set_state(0) - self._todo = any_to_name(WritePRD) - return self._rc.todo + self.todo_desc = any_to_name(WritePRD) + return bool(self._rc.todo) async def _observe(self, ignore_memory=False) -> int: return await super(ProductManager, self)._observe(ignore_memory=True) @@ -53,4 +53,4 @@ async def _observe(self, ignore_memory=False) -> int: @property def todo(self) -> str: """AgentStore uses this attribute to display to the user what actions the current role should take.""" - return self._todo + return self.todo_desc diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 8d229beec..992ff83d2 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -345,11 +345,6 @@ def set_env(self, env: "Environment"): env.set_subscription(self, self.subscription) self.refresh_system_message() # add env message to system message - @property - def subscription(self) -> Set: - """The labels for messages to be consumed by the Role object.""" - return set(self._subscription) - @property def action_count(self): """Return number of action""" diff --git a/metagpt/tools/web_browser_engine.py b/metagpt/tools/web_browser_engine.py index cda137cbd..ad753c634 100644 --- a/metagpt/tools/web_browser_engine.py +++ b/metagpt/tools/web_browser_engine.py @@ -6,7 +6,7 @@ from __future__ import annotations import importlib -from typing import Any, Callable, Coroutine, Dict, Literal, overload +from typing import Any, Callable, Coroutine, Literal, overload from metagpt.config import CONFIG from metagpt.tools import WebBrowserEngineType @@ -16,7 +16,6 @@ class WebBrowserEngine: def __init__( self, - options: Dict, engine: WebBrowserEngineType | None = None, run_func: Callable[..., Coroutine[Any, Any, WebPage | list[WebPage]]] | None = None, ): diff --git a/metagpt/utils/get_template.py b/metagpt/utils/get_template.py index 7e05e5d5e..b6dea00ae 100644 --- a/metagpt/utils/get_template.py +++ b/metagpt/utils/get_template.py @@ -8,7 +8,7 @@ from metagpt.config import CONFIG -def get_template(templates, schema=CONFIG.prompt_schema): +def get_template(templates, schema=CONFIG.prompt_format): selected_templates = templates.get(schema) if selected_templates is None: raise ValueError(f"Can't find {schema} in passed in templates") diff --git a/requirements.txt b/requirements.txt index aef886d3b..5144dc4a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,3 +60,4 @@ websockets~=12.0 networkx~=3.2.1 pylint~=3.0.3 google-generativeai==0.3.1 +playwright==1.40.0 \ No newline at end of file From e6a5e8e4ad4a64070125358e35ce421590f102fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sun, 24 Dec 2023 12:52:30 +0800 Subject: [PATCH 2/2] feat: merge geekan:dev --- metagpt/actions/research.py | 1 - 1 file changed, 1 deletion(-) diff --git a/metagpt/actions/research.py b/metagpt/actions/research.py index 2d2db4403..074cdee0a 100644 --- a/metagpt/actions/research.py +++ b/metagpt/actions/research.py @@ -181,7 +181,6 @@ class WebBrowseAndSummarize(Action): desc: str = "Explore the web and provide summaries of articles and webpages." browse_func: Union[Callable[[list[str]], None], None] = None web_browser_engine: WebBrowserEngine = WebBrowserEngine( - options={}, # FIXME: REMOVE options? engine=WebBrowserEngineType.CUSTOM if browse_func else None, run_func=browse_func, )