diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index 10579d4f4..5cf4f3d81 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -4,7 +4,7 @@ @Time : 2023/5/11 14:43 @Author : alexanderwu @File : action.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +@Modified By: mashenquan, 2023/8/20. Add function return annotations. """ from abc import ABC from typing import Optional @@ -12,15 +12,16 @@ from typing import Optional from tenacity import retry, stop_after_attempt, wait_fixed from metagpt.actions.action_output import ActionOutput -from metagpt.config import Config +from metagpt.llm import LLM from metagpt.utils.common import OutputParser from metagpt.logs import logger class Action(ABC): - def __init__(self, options=None, name: str = '', context=None, llm=None): - self.options = options or Config().runtime_options + def __init__(self, name: str = '', context=None, llm: LLM = None): self.name: str = name + if llm is None: + llm = LLM() self.llm = llm self.context = context self.prefix = "" diff --git a/metagpt/actions/action_output.py b/metagpt/actions/action_output.py index 6c812e7fe..917368798 100644 --- a/metagpt/actions/action_output.py +++ b/metagpt/actions/action_output.py @@ -4,6 +4,7 @@ @Time : 2023/7/11 10:03 @Author : chengmaoyu @File : action_output +@Modified By: mashenquan, 2023/8/20. Allow 'instruct_content' to be blank. """ from typing import Dict, Type, Optional diff --git a/metagpt/actions/analyze_dep_libs.py b/metagpt/actions/analyze_dep_libs.py index d7b251ead..23c35cdf8 100644 --- a/metagpt/actions/analyze_dep_libs.py +++ b/metagpt/actions/analyze_dep_libs.py @@ -4,7 +4,6 @@ @Time : 2023/5/19 12:01 @Author : alexanderwu @File : analyze_dep_libs.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions import Action @@ -27,8 +26,8 @@ Focus only on the names of shared dependencies, do not add any other explanation class AnalyzeDepLibs(Action): - def __init__(self, options, name, context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name, context=None, llm=None): + super().__init__(name, context, llm) self.desc = "根据上下文,分析程序运行依赖库" async def run(self, requirement, filepaths_string): diff --git a/metagpt/actions/debug_error.py b/metagpt/actions/debug_error.py index 78c970337..d69a22dba 100644 --- a/metagpt/actions/debug_error.py +++ b/metagpt/actions/debug_error.py @@ -4,7 +4,6 @@ @Time : 2023/5/11 17:46 @Author : alexanderwu @File : debug_error.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import re @@ -26,8 +25,8 @@ Now you should start rewriting the code: ## file name of the code to rewrite: Write code with triple quoto. Do your best to implement THIS IN ONLY ONE FILE. """ class DebugError(Action): - def __init__(self, options, name="DebugError", context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="DebugError", context=None, llm=None): + super().__init__(name, context, llm) # async def run(self, code, error): # prompt = f"Here is a piece of Python code:\n\n{code}\n\nThe following error occurred during execution:" \ diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index a01e1c753..cf23e6ad1 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -5,7 +5,6 @@ @Author : alexanderwu @File : design_api.py @Modified By: mashenquan, 2023-8-9, align `run` parameters with the parent :class:`Action` class. -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import shutil from pathlib import Path @@ -92,8 +91,8 @@ OUTPUT_MAPPING = { class WriteDesign(Action): - def __init__(self, options, name, context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + 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." @@ -108,15 +107,15 @@ class WriteDesign(Action): 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(options=self.options, mermaid_code=quadrant_chart, output_file_without_suffix=resources_path / 'competitive_analysis') + mermaid_to_file(quadrant_chart, resources_path / 'competitive_analysis') logger.info(f"Saving PRD to {prd_file}") prd_file.write_text(prd) 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(options=self.options, mermaid_code=data_api_design, output_file_without_suffix=resources_path / 'data_api_design') - mermaid_to_file(options=self.options, mermaid_code=seq_flow, output_file_without_suffix=resources_path / 'seq_flow') + 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) diff --git a/metagpt/actions/design_api_review.py b/metagpt/actions/design_api_review.py index ca4147cca..687a33652 100644 --- a/metagpt/actions/design_api_review.py +++ b/metagpt/actions/design_api_review.py @@ -4,14 +4,13 @@ @Time : 2023/5/11 19:31 @Author : alexanderwu @File : design_api_review.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions.action import Action class DesignReview(Action): - def __init__(self, options, name, context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name, context=None, llm=None): + super().__init__(name, context, llm) async def run(self, prd, api_design): prompt = f"Here is the Product Requirement Document (PRD):\n\n{prd}\n\nHere is the list of APIs designed " \ diff --git a/metagpt/actions/design_filenames.py b/metagpt/actions/design_filenames.py index 1f71e9530..6c3d8e803 100644 --- a/metagpt/actions/design_filenames.py +++ b/metagpt/actions/design_filenames.py @@ -4,7 +4,6 @@ @Time : 2023/5/19 11:50 @Author : alexanderwu @File : design_filenames.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions import Action from metagpt.logs import logger @@ -16,8 +15,8 @@ Do not add any other explanations, just return a Python string list.""" class DesignFilenames(Action): - def __init__(self, options, name, context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name, context=None, llm=None): + super().__init__(name, context, llm) self.desc = "Based on the PRD, consider system design, and carry out the basic design of the corresponding " \ "APIs, data structures, and database tables. Please give your design, feedback clearly and in detail." diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index d17bf6b03..16473ff01 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -5,7 +5,6 @@ @Author : alexanderwu @File : project_management.py @Modified By: mashenquan, 2023-8-9, align `run` parameters with the parent :class:`Action` class. -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from typing import List, Tuple @@ -105,8 +104,8 @@ OUTPUT_MAPPING = { class WriteTasks(Action): - def __init__(self, options, name="CreateTasks", context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="CreateTasks", context=None, llm=None): + super().__init__(name, context, llm) def _save(self, context, rsp): ws_name = CodeParser.parse_str(block="Python package name", text=context[-1].content) diff --git a/metagpt/actions/research.py b/metagpt/actions/research.py index 22b0eaa1d..81eb876dd 100644 --- a/metagpt/actions/research.py +++ b/metagpt/actions/research.py @@ -1,9 +1,5 @@ #!/usr/bin/env python -""" -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. -""" - from __future__ import annotations import asyncio @@ -13,6 +9,7 @@ from typing import Callable from pydantic import parse_obj_as from metagpt.actions import Action +from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.tools.search_engine import SearchEngine from metagpt.tools.web_browser_engine import WebBrowserEngine, WebBrowserEngineType @@ -82,15 +79,14 @@ class CollectLinks(Action): """Action class to collect links from a search engine.""" def __init__( self, - options, name: str = "", *args, rank_func: Callable[[list[str]], None] | None = None, **kwargs, ): - super().__init__(options=options, name=name, *args, **kwargs) + super().__init__(name, *args, **kwargs) self.desc = "Collect links from a search engine." - self.search_engine = SearchEngine(options=options) + self.search_engine = SearchEngine() self.rank_func = rank_func async def run( @@ -130,7 +126,7 @@ class CollectLinks(Action): remove.pop() if len(remove) == 0: break - prompt = reduce_message_length(gen_msg(), self.llm.model, system_text, self.options.get("max_tokens_rsp")) + prompt = reduce_message_length(gen_msg(), self.llm.model, system_text, CONFIG.max_tokens_rsp) logger.debug(prompt) queries = await self._aask(prompt, [system_text]) try: @@ -182,10 +178,9 @@ class WebBrowseAndSummarize(Action): **kwargs, ): super().__init__(*args, **kwargs) - if self.options.get("model_for_researcher_summary"): - self.llm.model = self.options.get("model_for_researcher_summary") + if CONFIG.model_for_researcher_summary: + self.llm.model = CONFIG.model_for_researcher_summary self.web_browser_engine = WebBrowserEngine( - options=self.options, engine=WebBrowserEngineType.CUSTOM if browse_func else None, run_func=browse_func, ) @@ -218,8 +213,7 @@ class WebBrowseAndSummarize(Action): for u, content in zip([url, *urls], contents): content = content.inner_text chunk_summaries = [] - for prompt in generate_prompt_chunk(content, prompt_template, self.llm.model, system_text, - self.options.get("max_tokens_rsp")): + for prompt in generate_prompt_chunk(content, prompt_template, self.llm.model, system_text, CONFIG.max_tokens_rsp): logger.debug(prompt) summary = await self._aask(prompt, [system_text]) if summary == "Not relevant.": @@ -245,8 +239,8 @@ class ConductResearch(Action): """Action class to conduct research and generate a research report.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if self.options.get("model_for_researcher_report"): - self.llm.model = self.options.get("model_for_researcher_report") + if CONFIG.model_for_researcher_report: + self.llm.model = CONFIG.model_for_researcher_report async def run( self, diff --git a/metagpt/actions/run_code.py b/metagpt/actions/run_code.py index 824ed83fa..f69d2cd1a 100644 --- a/metagpt/actions/run_code.py +++ b/metagpt/actions/run_code.py @@ -4,7 +4,6 @@ @Time : 2023/5/11 17:46 @Author : alexanderwu @File : run_code.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import os import subprocess @@ -58,8 +57,8 @@ standard errors: {errs}; class RunCode(Action): - def __init__(self, options, name="RunCode", context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="RunCode", context=None, llm=None): + super().__init__(name, context, llm) @classmethod async def run_text(cls, code) -> Tuple[str, str]: diff --git a/metagpt/actions/search_and_summarize.py b/metagpt/actions/search_and_summarize.py index 80d1c52e4..9f54587fa 100644 --- a/metagpt/actions/search_and_summarize.py +++ b/metagpt/actions/search_and_summarize.py @@ -101,16 +101,16 @@ You are a member of a professional butler team and will provide helpful suggesti class SearchAndSummarize(Action): - def __init__(self, options, name="", context=None, llm=None, engine=None, search_func=None): - self.engine = engine or options.get("search_engine") + def __init__(self, name="", context=None, llm=None, engine=None, search_func=None): + self.engine = engine or CONFIG.search_engine try: - self.search_engine = SearchEngine(options=options, engine=self.engine, run_func=search_func) + self.search_engine = SearchEngine(self.engine, run_func=search_func) except pydantic.ValidationError: self.search_engine = None self.result = "" - super().__init__(options=options, name=name, context=context, llm=llm) + super().__init__(name, context, llm) async def run(self, context: list[Message], system_text=SEARCH_AND_SUMMARIZE_SYSTEM) -> str: if self.search_engine is None: diff --git a/metagpt/actions/skill_action.py b/metagpt/actions/skill_action.py index 8cc7b6c42..c921a5f17 100644 --- a/metagpt/actions/skill_action.py +++ b/metagpt/actions/skill_action.py @@ -1,3 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/8/28 +@Author : mashenquan +@File : skill_action.py +@Desc : Call learned skill +""" + import ast import importlib @@ -7,8 +16,8 @@ from metagpt.logs import logger class ArgumentsParingAction(Action): - def __init__(self, options, last_talk: str, skill: Skill, context=None, llm=None, **kwargs): - super(ArgumentsParingAction, self).__init__(options=options, name='', context=context, llm=llm) + def __init__(self, last_talk: str, skill: Skill, context=None, llm=None, **kwargs): + super(ArgumentsParingAction, self).__init__(name='', context=context, llm=llm) self.skill = skill self.ask = last_talk self.rsp = None @@ -59,15 +68,15 @@ class ArgumentsParingAction(Action): class SkillAction(Action): - def __init__(self, options, skill: Skill, args: dict, context=None, llm=None, **kwargs): - super(SkillAction, self).__init__(options=options, name='', context=context, llm=llm) + def __init__(self, skill: Skill, args: dict, context=None, llm=None, **kwargs): + super(SkillAction, self).__init__(name='', context=context, llm=llm) self._skill = skill self._args = args self.rsp = None async def run(self, *args, **kwargs) -> str | ActionOutput | None: """Run action""" - self.rsp = self.find_and_call_function(self._skill.name, args=self._args, **self.options) + self.rsp = self.find_and_call_function(self._skill.name, args=self._args, **kwargs) return ActionOutput(content=self.rsp, instruct_content=self._skill.json()) @staticmethod diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py index 9a2a2f81a..cc122ef7a 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -4,7 +4,6 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : write_code.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions import WriteDesign from metagpt.actions.action import Action @@ -44,8 +43,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc class WriteCode(Action): - def __init__(self, options, name="WriteCode", context: list[Message] = None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="WriteCode", context: list[Message] = None, llm=None): + super().__init__(name, context, llm) def _is_invalid(self, filename): return any(i in filename for i in ["mp3", "wav"]) diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py index d256c6bcb..7f6a7a38e 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -4,7 +4,6 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : write_code_review.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions.action import Action @@ -63,8 +62,8 @@ FORMAT_EXAMPLE = """ class WriteCodeReview(Action): - def __init__(self, options, name="WriteCodeReview", context: list[Message] = None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="WriteCodeReview", context: list[Message] = None, llm=None): + super().__init__(name, context, llm) @retry(stop=stop_after_attempt(2), wait=wait_fixed(1)) async def write_code(self, prompt): diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 794d3ee9d..0edd24d55 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -4,7 +4,6 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : write_prd.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from typing import List, Tuple @@ -128,11 +127,11 @@ OUTPUT_MAPPING = { class WritePRD(Action): - def __init__(self, options, name="", context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="", context=None, llm=None): + super().__init__(name, context, llm) async def run(self, requirements, *args, **kwargs) -> ActionOutput: - sas = SearchAndSummarize(options=self.options, llm=self.llm) + 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}" diff --git a/metagpt/actions/write_prd_review.py b/metagpt/actions/write_prd_review.py index 8c22f9c0a..5ff9624c5 100644 --- a/metagpt/actions/write_prd_review.py +++ b/metagpt/actions/write_prd_review.py @@ -4,14 +4,13 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : write_prd_review.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions.action import Action class WritePRDReview(Action): - def __init__(self, options, name, context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name, context=None, llm=None): + super().__init__(name, context, llm) self.prd = None self.desc = "Based on the PRD, conduct a PRD Review, providing clear and detailed feedback" self.prd_review_prompt_template = """ diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py index 53371b5a1..bd8507350 100644 --- a/metagpt/actions/write_teaching_plan.py +++ b/metagpt/actions/write_teaching_plan.py @@ -42,7 +42,7 @@ class WriteTeachingPlanPart(Action): statements = [] from metagpt.roles import Role for p in statement_patterns: - s = Role.format_value(p, kwargs) + s = Role.format_value(p) statements.append(s) formatter = self.PROMPT_TITLE_TEMPLATE if self.topic == self.COURSE_TITLE else self.PROMPT_TEMPLATE prompt = formatter.format(formation=self.FORMATION, diff --git a/metagpt/actions/write_test.py b/metagpt/actions/write_test.py index 94006005f..5e50fdb55 100644 --- a/metagpt/actions/write_test.py +++ b/metagpt/actions/write_test.py @@ -4,7 +4,6 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : write_test.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from metagpt.actions.action import Action from metagpt.utils.common import CodeParser @@ -31,8 +30,8 @@ you should correctly import the necessary classes based on these file locations! class WriteTest(Action): - def __init__(self, options, name="WriteTest", context=None, llm=None): - super().__init__(options=options, name=name, context=context, llm=llm) + def __init__(self, name="WriteTest", context=None, llm=None): + super().__init__(name, context, llm) async def write_code(self, prompt): code_rsp = await self._aask(prompt) diff --git a/metagpt/learn/skill_metadata.py b/metagpt/learn/skill_metadata.py deleted file mode 100644 index dea5fb04d..000000000 --- a/metagpt/learn/skill_metadata.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -@Time : 2023/8/20 -@Author : mashenquan -@File : skill_metadata.py -@Desc : Defines metadata for the `skill`. - Depending on the context and specific circumstances, skills may have different effects. - For example: - Proprietor: "Skill of the proprietor entity." - Holder: "Skill of the holder entity." - Possessor: "Skill of the possessor entity." - Controller: "Skill of the controller entity." - Owner: "Skill of the owner entity." -""" - - -def skill_metadata(name, description, requisite): - def decorator(func): - func.skill_name = name - func.skill_description = description - func.skill_requisite = requisite - return func - - return decorator diff --git a/metagpt/learn/text_to_embedding.py b/metagpt/learn/text_to_embedding.py index 5c08ef0b9..26dab0419 100644 --- a/metagpt/learn/text_to_embedding.py +++ b/metagpt/learn/text_to_embedding.py @@ -6,16 +6,11 @@ @File : text_to_embedding.py @Desc : Text-to-Embedding skill, which provides text-to-embedding functionality. """ -import os -from metagpt.learn.skill_metadata import skill_metadata +from metagpt.config import CONFIG from metagpt.tools.openai_text_to_embedding import oas3_openai_text_to_embedding -from metagpt.utils.common import initialize_environment -@skill_metadata(name="Text to Embedding", - description="Convert the text into embeddings.", - requisite="`OPENAI_API_KEY`") async def text_to_embedding(text, model="text-embedding-ada-002", openai_api_key="", **kwargs): """Text to embedding @@ -24,7 +19,6 @@ async def text_to_embedding(text, model="text-embedding-ada-002", openai_api_key :param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys` :return: A json object of :class:`ResultEmbedding` class if successful, otherwise `{}`. """ - initialize_environment() - if os.environ.get("OPENAI_API_KEY") or openai_api_key: + if CONFIG.OPENAI_API_KEY or openai_api_key: return await oas3_openai_text_to_embedding(text, model=model, openai_api_key=openai_api_key) raise EnvironmentError diff --git a/metagpt/learn/text_to_image.py b/metagpt/learn/text_to_image.py index db9844c71..2762c2f18 100644 --- a/metagpt/learn/text_to_image.py +++ b/metagpt/learn/text_to_image.py @@ -8,15 +8,11 @@ """ import os -from metagpt.learn.skill_metadata import skill_metadata +from metagpt.config import CONFIG from metagpt.tools.metagpt_text_to_image import oas3_metagpt_text_to_image from metagpt.tools.openai_text_to_image import oas3_openai_text_to_image -from metagpt.utils.common import initialize_environment -@skill_metadata(name="Text to image", - description="Create a drawing based on the text.", - requisite="`OPENAI_API_KEY` or `METAGPT_TEXT_TO_IMAGE_MODEL`") async def text_to_image(text, size_type: str = "512x512", openai_api_key="", model_url="", **kwargs): """Text to image @@ -26,13 +22,12 @@ async def text_to_image(text, size_type: str = "512x512", openai_api_key="", mod :param model_url: MetaGPT model url :return: The image data is returned in Base64 encoding. """ - initialize_environment() image_declaration = "data:image/png;base64," - if os.environ.get("METAGPT_TEXT_TO_IMAGE_MODEL_URL") or model_url: + if CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL or model_url: data = await oas3_metagpt_text_to_image(text, size_type, model_url) return image_declaration + data if data else "" - if os.environ.get("OPENAI_API_KEY") or openai_api_key: + if CONFIG.OPENAI_API_KEY or openai_api_key: data = await oas3_openai_text_to_image(text, size_type, openai_api_key) return image_declaration + data if data else "" diff --git a/metagpt/learn/text_to_speech.py b/metagpt/learn/text_to_speech.py index e5eb3d488..ba73de04c 100644 --- a/metagpt/learn/text_to_speech.py +++ b/metagpt/learn/text_to_speech.py @@ -6,16 +6,14 @@ @File : text_to_speech.py @Desc : Text-to-Speech skill, which provides text-to-speech functionality """ -import os -from metagpt.learn.skill_metadata import skill_metadata + +from metagpt.config import CONFIG + from metagpt.tools.azure_tts import oas3_azsure_tts -from metagpt.utils.common import initialize_environment -@skill_metadata(name="Text to speech", - description="Text-to-speech", - requisite="`AZURE_TTS_SUBSCRIPTION_KEY` and `AZURE_TTS_REGION`") + async def text_to_speech(text, lang="zh-CN", voice="zh-CN-XiaomoNeural", style="affectionate", role="Girl", subscription_key="", region="", **kwargs): """Text to speech @@ -31,9 +29,8 @@ async def text_to_speech(text, lang="zh-CN", voice="zh-CN-XiaomoNeural", style=" :return: Returns the Base64-encoded .wav file data if successful, otherwise an empty string. """ - initialize_environment() audio_declaration = "data:audio/wav;base64," - if (os.environ.get("AZURE_TTS_SUBSCRIPTION_KEY") and os.environ.get("AZURE_TTS_REGION")) or \ + if (CONFIG.AZURE_TTS_SUBSCRIPTION_KEY and CONFIG.AZURE_TTS_REGION) or \ (subscription_key and region): data = await oas3_azsure_tts(text, lang, voice, style, role, subscription_key, region) return audio_declaration + data if data else data diff --git a/metagpt/llm.py b/metagpt/llm.py new file mode 100644 index 000000000..6a9a9132f --- /dev/null +++ b/metagpt/llm.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/11 14:45 +@Author : alexanderwu +@File : llm.py +""" + +from metagpt.provider.anthropic_api import Claude2 as Claude +from metagpt.provider.openai_api import OpenAIGPTAPI as LLM + +DEFAULT_LLM = LLM() +CLAUDE_LLM = Claude() + + +async def ai_func(prompt): + """使用LLM进行QA + QA with LLMs + """ + return await DEFAULT_LLM.aask(prompt) diff --git a/metagpt/manager.py b/metagpt/manager.py index c4565808e..9d238c621 100644 --- a/metagpt/manager.py +++ b/metagpt/manager.py @@ -4,15 +4,14 @@ @Time : 2023/5/11 14:42 @Author : alexanderwu @File : manager.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ - +from metagpt.llm import LLM from metagpt.logs import logger from metagpt.schema import Message class Manager: - def __init__(self, llm): + def __init__(self, llm: LLM = LLM()): self.llm = llm # Large Language Model self.role_directions = { "BOSS": "Product Manager", diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index 5a498c50b..00b6cb2eb 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -4,8 +4,6 @@ @Time : 2023/5/11 14:43 @Author : alexanderwu @File : architect.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation; - Change cost control from global to company level. """ from metagpt.actions import WriteDesign, WritePRD @@ -14,8 +12,8 @@ from metagpt.roles import Role class Architect(Role): """Architect: Listen to PRD, responsible for designing API, designing code files""" - def __init__(self, options, cost_manager, name="Bob", profile="Architect", goal="Design a concise, usable, complete python system", + def __init__(self, name="Bob", profile="Architect", goal="Design a concise, usable, complete python system", constraints="Try to specify good open source tools as much as possible"): - super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) + super().__init__(name, profile, goal, constraints) self._init_actions([WriteDesign]) self._watch({WritePRD}) diff --git a/metagpt/roles/customer_service.py b/metagpt/roles/customer_service.py index 8550313d4..4aae7cb03 100644 --- a/metagpt/roles/customer_service.py +++ b/metagpt/roles/customer_service.py @@ -26,11 +26,9 @@ DESC = """ class CustomerService(Sales): def __init__( self, - options, - cost_manager, name="Xiaomei", profile="Human customer service", desc=DESC, store=None ): - super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, desc=desc, store=store) + super().__init__(name, profile, desc=desc, store=store) diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 9da2b5a09..072e53998 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -47,10 +47,10 @@ async def gather_ordered_k(coros, k) -> list: class Engineer(Role): - def __init__(self, options, cost_manager, name="Alex", profile="Engineer", goal="Write elegant, readable, extensible, efficient code", + def __init__(self, name="Alex", profile="Engineer", goal="Write elegant, readable, extensible, efficient code", constraints="The code you write should conform to code standard like PEP8, be modular, easy to read and maintain", n_borg=1, use_code_review=False): - super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) + super().__init__(name, profile, goal, constraints) self._init_actions([WriteCode]) self.use_code_review = use_code_review if self.use_code_review: @@ -131,7 +131,7 @@ class Engineer(Role): async def _act_sp(self) -> Message: code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later for todo in self.todos: - code = await WriteCode(options=self.options, llm=self._llm).run( + code = await WriteCode().run( context=self._rc.history, filename=todo ) diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index bb69c8dfd..b42e9bb29 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/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation; - Change cost control from global to company level. """ from metagpt.actions import BossRequirement, WritePRD from metagpt.roles import Role class ProductManager(Role): - def __init__(self, options, cost_manager, name="Alice", profile="Product Manager", goal="Efficiently create a successful product", + def __init__(self, name="Alice", profile="Product Manager", goal="Efficiently create a successful product", constraints=""): - super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) + super().__init__(name, profile, goal, constraints) self._init_actions([WritePRD]) self._watch([BossRequirement]) diff --git a/metagpt/roles/project_manager.py b/metagpt/roles/project_manager.py index 3e8b36550..ff374de13 100644 --- a/metagpt/roles/project_manager.py +++ b/metagpt/roles/project_manager.py @@ -4,16 +4,14 @@ @Time : 2023/5/11 15:04 @Author : alexanderwu @File : project_manager.py -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation; - Change cost control from global to company level. """ from metagpt.actions import WriteDesign, WriteTasks from metagpt.roles import Role class ProjectManager(Role): - def __init__(self, options, cost_manager, name="Eve", profile="Project Manager", + def __init__(self, name="Eve", profile="Project Manager", goal="Improve team efficiency and deliver with quality and quantity", constraints=""): - super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) + super().__init__(name, profile, goal, constraints) self._init_actions([WriteTasks]) self._watch([WriteDesign]) diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index ac5df0dbd..65bf2cc5b 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -20,15 +20,13 @@ from metagpt.utils.special_tokens import FILENAME_CODE_SEP, MSG_SEP class QaEngineer(Role): def __init__( self, - options, - cost_manager, name="Edward", profile="QaEngineer", goal="Write comprehensive and robust tests to ensure codes will work as expected without bugs", constraints="The test code you write should conform to code standard like PEP8, be modular, easy to read and maintain", test_round_allowed=5, ): - super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) + super().__init__(name, profile, goal, constraints) self._init_actions( [WriteTest] ) # FIXME: a bit hack here, only init one action to circumvent _think() logic, will overwrite _think() in future updates diff --git a/metagpt/roles/researcher.py b/metagpt/roles/researcher.py index f3ff7f8e5..cb4d28c33 100644 --- a/metagpt/roles/researcher.py +++ b/metagpt/roles/researcher.py @@ -26,8 +26,6 @@ class Report(BaseModel): class Researcher(Role): def __init__( self, - options, - cost_manager, name: str = "David", profile: str = "Researcher", goal: str = "Gather information and conduct research", @@ -35,11 +33,8 @@ class Researcher(Role): language: str = "en-us", **kwargs, ): - super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, goal=goal, constraints=constraints, **kwargs) - self._init_actions([ - CollectLinks(options=options, name=name), - WebBrowseAndSummarize(options=options, name=name), - ConductResearch(options=options, name=name)]) + super().__init__(name, profile, goal, constraints, **kwargs) + self._init_actions([CollectLinks(name), WebBrowseAndSummarize(name), ConductResearch(name)]) self.language = language if language not in ("en-us", "zh-cn"): logger.warning(f"The language `{language}` has not been tested, it may not work.") diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 4f46bb973..a1ac0d9e7 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -4,9 +4,7 @@ @Time : 2023/5/11 14:42 @Author : alexanderwu @File : role.py -@Modified By: mashenquan, 2023-8-7, :class:`Role` + properties. -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation; - Change cost control from global to company level. +@Modified By: mashenquan, 2023-8-7, Support template-style variables, such as '{teaching_language} Teacher'. @Modified By: mashenquan, 2023/8/22. A definition has been provided for the return value of _think: returning false indicates that further reasoning cannot continue. """ from __future__ import annotations @@ -15,7 +13,8 @@ from typing import Iterable, Type, Dict from pydantic import BaseModel, Field -from metagpt.config import Config +from metagpt.config import Config, CONFIG +from metagpt.const import OPTIONS from metagpt.provider.openai_api import OpenAIGPTAPI as LLM, CostManager from metagpt.actions import Action, ActionOutput from metagpt.logs import logger @@ -74,13 +73,12 @@ class RoleContext(BaseModel): todo: Action = Field(default=None) watch: set[Type[Action]] = Field(default_factory=set) news: list[Type[Message]] = Field(default=[]) - options: Dict class Config: arbitrary_types_allowed = True def check(self, role_id: str): - if self.options.get("long_term_memory"): + if CONFIG.long_term_memory: self.long_term_memory.recover_memory(role_id, self) self.memory = self.long_term_memory # use memory to act as long_term_memory for unify operation @@ -102,26 +100,20 @@ class RoleContext(BaseModel): class Role: """Role/Proxy""" - def __init__(self, options=None, cost_manager=None, name="", profile="", goal="", constraints="", desc="", *args, **kwargs): - options = options or Config().runtime_options - cost_manager = cost_manager or CostManager(*options) - - self._options = Role.supply_options(options=kwargs, default_options=options) - - name = Role.format_value(name, self._options) - profile = Role.format_value(profile, self._options) - goal = Role.format_value(goal, self._options) - constraints = Role.format_value(constraints, self._options) - desc = Role.format_value(desc, self._options) - - self._cost_manager = cost_manager - self._llm = LLM(options=self._options, cost_manager=cost_manager) + def __init__(self, name="", profile="", goal="", constraints="", desc="", *args, **kwargs): + # Replace template-style variables, such as '{teaching_language} Teacher'. + name = Role.format_value(name) + profile = Role.format_value(profile) + goal = Role.format_value(goal) + constraints = Role.format_value(constraints) + desc = Role.format_value(desc) + self._llm = LLM() self._setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc) self._states = [] self._actions = [] self._role_id = str(self._setting) - self._rc = RoleContext(options=self._options) + self._rc = RoleContext() def _reset(self): self._states = [] @@ -131,7 +123,7 @@ class Role: self._reset() for idx, action in enumerate(actions): if not isinstance(action, Action): - i = action(options=self._options, name="", llm=self._llm) + i = action("", llm=self._llm) else: i = action i.set_prefix(self._get_prefix(), self.profile) @@ -184,14 +176,6 @@ class Role: """Return number of action""" return len(self._actions) - @property - def options(self): - return self._options - - @options.setter - def options(self, opts): - self._options.update(opts) - def _get_prefix(self): """获取角色前缀""" if self._setting.desc: @@ -222,7 +206,7 @@ class Role: logger.info(f"{self._setting}: ready to {self._rc.todo}") requirement = self._rc.important_memory or self._rc.prerequisite - response = await self._rc.todo.run(requirement, **self._options) + response = await self._rc.todo.run(requirement) # logger.info(response) if isinstance(response, ActionOutput): msg = Message(content=response.content, instruct_content=response.instruct_content, @@ -300,23 +284,14 @@ class Role: return rsp @staticmethod - def supply_options(options, default_options=None): - """Supply missing options""" - ret = default_options.copy() if default_options else {} - if not options: - return ret - ret.update(options) - return ret - - @staticmethod - def format_value(value, opts, default_opts=None): + def format_value(value): """Fill parameters inside `value` with `options`.""" if not isinstance(value, str): return value if "{" not in value: return value - merged_opts = Role.supply_options(opts, default_opts) + merged_opts = OPTIONS.get() or {} try: return value.format(**merged_opts) except KeyError as e: diff --git a/metagpt/roles/sales.py b/metagpt/roles/sales.py index 35146fdc3..51b13f487 100644 --- a/metagpt/roles/sales.py +++ b/metagpt/roles/sales.py @@ -13,8 +13,6 @@ from metagpt.tools import SearchEngineType class Sales(Role): def __init__( self, - options, - cost_manager, name="Xiaomei", profile="Retail sales guide", desc="I am a sales guide in retail. My name is Xiaomei. I will answer some customer questions next, and I " @@ -25,7 +23,7 @@ class Sales(Role): "professional guide", store=None ): - super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, desc=desc) + super().__init__(name, profile, desc=desc) self._set_store(store) def _set_store(self, store): diff --git a/metagpt/roles/seacher.py b/metagpt/roles/seacher.py index 7b07ce713..c116ce98b 100644 --- a/metagpt/roles/seacher.py +++ b/metagpt/roles/seacher.py @@ -13,9 +13,9 @@ from metagpt.tools import SearchEngineType class Searcher(Role): - def __init__(self, options, cost_manager, name='Alice', profile='Smart Assistant', goal='Provide search services for users', + def __init__(self, name='Alice', profile='Smart Assistant', goal='Provide search services for users', constraints='Answer is rich and complete', engine=SearchEngineType.SERPAPI_GOOGLE, **kwargs): - super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, goal=goal, constraints=constraints, **kwargs) + super().__init__(name, profile, goal, constraints, **kwargs) self._init_actions([SearchAndSummarize(engine=engine)]) def set_search_func(self, search_func): diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py index d2a2198f5..ca88fd681 100644 --- a/metagpt/roles/teacher.py +++ b/metagpt/roles/teacher.py @@ -22,13 +22,13 @@ import re class Teacher(Role): """Support configurable teacher roles, with native and teaching languages being replaceable through configurations.""" - def __init__(self, options, name='Lily', profile='{teaching_language} Teacher', + def __init__(self, name='Lily', profile='{teaching_language} Teacher', goal='writing a {language} teaching plan part by part', constraints='writing in {language}', desc="", *args, **kwargs): - super().__init__(options=options, name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs) + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs) actions = [] for topic in WriteTeachingPlanPart.TOPICS: - act = WriteTeachingPlanPart(options=options, topic=topic, llm=self._llm) + act = WriteTeachingPlanPart(topic=topic, llm=self._llm) actions.append(act) self._init_actions(actions) self._watch({TeachingPlanRequirement}) diff --git a/metagpt/software_company.py b/metagpt/software_company.py index 529dc0fe7..8f173ebf3 100644 --- a/metagpt/software_company.py +++ b/metagpt/software_company.py @@ -4,22 +4,16 @@ @Time : 2023/5/12 00:30 @Author : alexanderwu @File : software_company.py -@Modified By: mashenquan, 2023-07-27, Add `role` & `cause_by` parameters to `start_project()`. -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation; - Change cost control from global to company level. """ -from typing import Dict - from pydantic import BaseModel, Field from metagpt.actions import BossRequirement +from metagpt.config import CONFIG from metagpt.environment import Environment from metagpt.logs import logger -from metagpt.provider.openai_api import CostManager from metagpt.roles import Role from metagpt.schema import Message from metagpt.utils.common import NoMoneyException -from metagpt.config import Config class SoftwareCompany(BaseModel): @@ -30,8 +24,6 @@ class SoftwareCompany(BaseModel): environment: Environment = Field(default_factory=Environment) investment: float = Field(default=10.0) idea: str = Field(default="") - options: Dict = Field(default=Config().runtime_options) - cost_manager: CostManager = Field(default=CostManager(**Config().runtime_options)) class Config: arbitrary_types_allowed = True @@ -43,17 +35,17 @@ class SoftwareCompany(BaseModel): def invest(self, investment: float): """Invest company. raise NoMoneyException when exceed max_budget.""" self.investment = investment - self.options["max_budget"] = investment + CONFIG.max_budget = investment logger.info(f'Investment: ${investment}.') def _check_balance(self): - if self.total_cost > self.max_budget: - raise NoMoneyException(self.total_cost, f'Insufficient funds: {self.max_budget}') + if CONFIG.total_cost > CONFIG.max_budget: + raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}') - def start_project(self, idea, role="BOSS", cause_by=BossRequirement): + def start_project(self, idea): """Start a project from publishing boss requirement.""" self.idea = idea - self.environment.publish_message(Message(role=role, content=idea, cause_by=cause_by)) + self.environment.publish_message(Message(role="BOSS", content=idea, cause_by=BossRequirement)) def _save(self): logger.info(self.json()) @@ -67,13 +59,3 @@ class SoftwareCompany(BaseModel): self._check_balance() await self.environment.run() return self.environment.history - - @property - def max_budget(self): - return self.options.get("max_budget", 0) - - @property - def total_cost(self): - return self.options.get("total_cost", 0) - - diff --git a/metagpt/tools/openai_text_to_embedding.py b/metagpt/tools/openai_text_to_embedding.py index 119eb35b6..73984aff6 100644 --- a/metagpt/tools/openai_text_to_embedding.py +++ b/metagpt/tools/openai_text_to_embedding.py @@ -17,8 +17,9 @@ import requests from pydantic import BaseModel import sys +from metagpt.config import CONFIG + sys.path.append(str(Path(__file__).resolve().parent.parent.parent)) # fix-bug: No module named 'metagpt' -from metagpt.utils.common import initialize_environment from metagpt.logs import logger @@ -83,12 +84,11 @@ async def oas3_openai_text_to_embedding(text, model="text-embedding-ada-002", op if not text: return "" if not openai_api_key: - openai_api_key = os.environ.get("OPENAI_API_KEY") + openai_api_key = CONFIG.OPENAI_API_KEY return await OpenAIText2Embedding(openai_api_key).text_2_embedding(text, model=model) if __name__ == "__main__": - initialize_environment() loop = asyncio.new_event_loop() v = loop.create_task(oas3_openai_text_to_embedding("Panda emoji")) loop.run_until_complete(v) diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index a6e4dc20d..791bb2767 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -259,18 +259,3 @@ def parse_recipient(text): recipient = re.search(pattern, text) return recipient.group(1) if recipient else "" - -def initialize_environment(options=None): - """Load `config/config.yaml` to `os.environ`""" - if options: - for k, v in options.items(): - os.environ[k] = str(v) - return - - yaml_file_path = Path(__file__).resolve().parent.parent.parent / "config/config.yaml" - if not yaml_file_path.exists(): - return - with open(str(yaml_file_path), "r") as yaml_file: - data = yaml.safe_load(yaml_file) - for k, v in data.items(): - os.environ[k] = str(v) diff --git a/startup.py b/startup.py index 84cd43956..03b2149c4 100644 --- a/startup.py +++ b/startup.py @@ -1,10 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -""" -@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation; - Change cost control from global to company level. -""" - import asyncio import platform import fire @@ -16,15 +11,14 @@ from metagpt.software_company import SoftwareCompany async def startup(idea: str, investment: float = 3.0, n_round: int = 5, code_review: bool = False, run_tests: bool = False): """Run a startup. Be a boss.""" - company = SoftwareCompany() - company.hire([ProductManager(options=company.options, cost_manager=company.cost_manager), - Architect(options=company.options, cost_manager=company.cost_manager), - ProjectManager(options=company.options, cost_manager=company.cost_manager), - Engineer(n_borg=5, use_code_review=code_review, options=company.options, cost_manager=company.cost_manager)]) + company.hire([ProductManager(), + Architect(), + ProjectManager(), + Engineer(n_borg=5, use_code_review=code_review)]) if run_tests: # developing features: run tests on the spot and identify bugs (bug fixing capability comes soon!) - company.hire([QaEngineer(options=company.options, cost_manager=company.cost_manager)]) + company.hire([QaEngineer()]) company.invest(investment) company.start_project(idea) await company.run(n_round=n_round)