diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index fa0d592a3..899c2515c 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -4,6 +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. """ from abc import ABC from typing import Optional @@ -11,15 +12,14 @@ from typing import Optional from tenacity import retry, stop_after_attempt, wait_fixed from metagpt.actions.action_output import ActionOutput -from metagpt.llm import LLM from metagpt.utils.common import OutputParser from metagpt.logs import logger + class Action(ABC): - def __init__(self, name: str = '', context=None, llm: LLM = None): + def __init__(self, options, name: str = '', context=None, llm=None): + self.options = options self.name: str = name - if llm is None: - llm = LLM() self.llm = llm self.context = context self.prefix = "" diff --git a/metagpt/actions/analyze_dep_libs.py b/metagpt/actions/analyze_dep_libs.py index 23c35cdf8..d7b251ead 100644 --- a/metagpt/actions/analyze_dep_libs.py +++ b/metagpt/actions/analyze_dep_libs.py @@ -4,6 +4,7 @@ @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 @@ -26,8 +27,8 @@ Focus only on the names of shared dependencies, do not add any other explanation class AnalyzeDepLibs(Action): - def __init__(self, name, context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name, context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 d69a22dba..78c970337 100644 --- a/metagpt/actions/debug_error.py +++ b/metagpt/actions/debug_error.py @@ -4,6 +4,7 @@ @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 @@ -25,8 +26,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, name="DebugError", context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="DebugError", context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 1447eacc3..eb08cb9f0 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -4,6 +4,7 @@ @Time : 2023/5/11 19:26 @Author : alexanderwu @File : design_api.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import shutil from pathlib import Path @@ -90,8 +91,8 @@ OUTPUT_MAPPING = { class WriteDesign(Action): - def __init__(self, name, context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name, context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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." @@ -106,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(quadrant_chart, resources_path / 'competitive_analysis') + mermaid_to_file(options=self.options, mermaid_code=quadrant_chart, output_file_without_suffix=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(data_api_design, resources_path / 'data_api_design') - mermaid_to_file(seq_flow, resources_path / 'seq_flow') + 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') 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 687a33652..ca4147cca 100644 --- a/metagpt/actions/design_api_review.py +++ b/metagpt/actions/design_api_review.py @@ -4,13 +4,14 @@ @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, name, context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name, context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 6c3d8e803..1f71e9530 100644 --- a/metagpt/actions/design_filenames.py +++ b/metagpt/actions/design_filenames.py @@ -4,6 +4,7 @@ @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 @@ -15,8 +16,8 @@ Do not add any other explanations, just return a Python string list.""" class DesignFilenames(Action): - def __init__(self, name, context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name, context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 89c59dcda..3d8aa9322 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -4,6 +4,7 @@ @Time : 2023/5/11 19:12 @Author : alexanderwu @File : project_management.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from typing import List, Tuple @@ -103,8 +104,8 @@ OUTPUT_MAPPING = { class WriteTasks(Action): - def __init__(self, name="CreateTasks", context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="CreateTasks", context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 81eb876dd..22b0eaa1d 100644 --- a/metagpt/actions/research.py +++ b/metagpt/actions/research.py @@ -1,5 +1,9 @@ #!/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 @@ -9,7 +13,6 @@ 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 @@ -79,14 +82,15 @@ 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__(name, *args, **kwargs) + super().__init__(options=options, name=name, *args, **kwargs) self.desc = "Collect links from a search engine." - self.search_engine = SearchEngine() + self.search_engine = SearchEngine(options=options) self.rank_func = rank_func async def run( @@ -126,7 +130,7 @@ class CollectLinks(Action): remove.pop() if len(remove) == 0: break - prompt = reduce_message_length(gen_msg(), self.llm.model, system_text, CONFIG.max_tokens_rsp) + prompt = reduce_message_length(gen_msg(), self.llm.model, system_text, self.options.get("max_tokens_rsp")) logger.debug(prompt) queries = await self._aask(prompt, [system_text]) try: @@ -178,9 +182,10 @@ class WebBrowseAndSummarize(Action): **kwargs, ): super().__init__(*args, **kwargs) - if CONFIG.model_for_researcher_summary: - self.llm.model = CONFIG.model_for_researcher_summary + if self.options.get("model_for_researcher_summary"): + self.llm.model = self.options.get("model_for_researcher_summary") self.web_browser_engine = WebBrowserEngine( + options=self.options, engine=WebBrowserEngineType.CUSTOM if browse_func else None, run_func=browse_func, ) @@ -213,7 +218,8 @@ 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, CONFIG.max_tokens_rsp): + for prompt in generate_prompt_chunk(content, prompt_template, self.llm.model, system_text, + self.options.get("max_tokens_rsp")): logger.debug(prompt) summary = await self._aask(prompt, [system_text]) if summary == "Not relevant.": @@ -239,8 +245,8 @@ class ConductResearch(Action): """Action class to conduct research and generate a research report.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if CONFIG.model_for_researcher_report: - self.llm.model = CONFIG.model_for_researcher_report + if self.options.get("model_for_researcher_report"): + self.llm.model = self.options.get("model_for_researcher_report") async def run( self, diff --git a/metagpt/actions/run_code.py b/metagpt/actions/run_code.py index f69d2cd1a..824ed83fa 100644 --- a/metagpt/actions/run_code.py +++ b/metagpt/actions/run_code.py @@ -4,6 +4,7 @@ @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 @@ -57,8 +58,8 @@ standard errors: {errs}; class RunCode(Action): - def __init__(self, name="RunCode", context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="RunCode", context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 5e4cdaea0..80d1c52e4 100644 --- a/metagpt/actions/search_and_summarize.py +++ b/metagpt/actions/search_and_summarize.py @@ -4,11 +4,11 @@ @Time : 2023/5/23 17:26 @Author : alexanderwu @File : search_google.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import pydantic from metagpt.actions import Action -from metagpt.config import Config from metagpt.logs import logger from metagpt.schema import Message from metagpt.tools.search_engine import SearchEngine @@ -101,17 +101,16 @@ You are a member of a professional butler team and will provide helpful suggesti class SearchAndSummarize(Action): - def __init__(self, name="", context=None, llm=None, engine=None, search_func=None): - self.config = Config() - self.engine = engine or self.config.search_engine + def __init__(self, options, name="", context=None, llm=None, engine=None, search_func=None): + self.engine = engine or options.get("search_engine") try: - self.search_engine = SearchEngine(self.engine, run_func=search_func) + self.search_engine = SearchEngine(options=options, engine=self.engine, run_func=search_func) except pydantic.ValidationError: self.search_engine = None self.result = "" - super().__init__(name, context, llm) + super().__init__(options=options, name=name, context=context, llm=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/write_code.py b/metagpt/actions/write_code.py index cc122ef7a..9a2a2f81a 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -4,6 +4,7 @@ @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 @@ -43,8 +44,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc class WriteCode(Action): - def __init__(self, name="WriteCode", context: list[Message] = None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="WriteCode", context: list[Message] = None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 7f6a7a38e..d256c6bcb 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -4,6 +4,7 @@ @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 @@ -62,8 +63,8 @@ FORMAT_EXAMPLE = """ class WriteCodeReview(Action): - def __init__(self, name="WriteCodeReview", context: list[Message] = None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="WriteCodeReview", context: list[Message] = None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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 0edd24d55..794d3ee9d 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -4,6 +4,7 @@ @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 @@ -127,11 +128,11 @@ OUTPUT_MAPPING = { class WritePRD(Action): - def __init__(self, name="", context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="", context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=llm) async def run(self, requirements, *args, **kwargs) -> ActionOutput: - sas = SearchAndSummarize() + sas = SearchAndSummarize(options=self.options, llm=self.llm) # 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 5ff9624c5..8c22f9c0a 100644 --- a/metagpt/actions/write_prd_review.py +++ b/metagpt/actions/write_prd_review.py @@ -4,13 +4,14 @@ @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, name, context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name, context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=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_test.py b/metagpt/actions/write_test.py index 5e50fdb55..94006005f 100644 --- a/metagpt/actions/write_test.py +++ b/metagpt/actions/write_test.py @@ -4,6 +4,7 @@ @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 @@ -30,8 +31,8 @@ you should correctly import the necessary classes based on these file locations! class WriteTest(Action): - def __init__(self, name="WriteTest", context=None, llm=None): - super().__init__(name, context, llm) + def __init__(self, options, name="WriteTest", context=None, llm=None): + super().__init__(options=options, name=name, context=context, llm=llm) async def write_code(self, prompt): code_rsp = await self._aask(prompt) diff --git a/metagpt/config.py b/metagpt/config.py index 6e2cf0a3f..076bc5eb7 100644 --- a/metagpt/config.py +++ b/metagpt/config.py @@ -118,8 +118,8 @@ class Config: return value @property - def options(self): - """Return key-value configuration parameters.""" + def runtime_options(self): + """Runtime key-value configuration parameters.""" opts = {} for k, v in self._configs.items(): opts[k] = v diff --git a/metagpt/document_store/faiss_store.py b/metagpt/document_store/faiss_store.py index 051bc2507..d15eb4c21 100644 --- a/metagpt/document_store/faiss_store.py +++ b/metagpt/document_store/faiss_store.py @@ -4,6 +4,7 @@ @Time : 2023/5/25 10:20 @Author : alexanderwu @File : faiss_store.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import pickle from pathlib import Path @@ -36,8 +37,11 @@ class FaissStore(LocalStore): store.index = index return store - def _write(self, docs, metadatas): - store = FAISS.from_texts(docs, OpenAIEmbeddings(openai_api_version="2020-11-07"), metadatas=metadatas) + def _write(self, docs, metadatas, **kwargs): + store = FAISS.from_texts(docs, + OpenAIEmbeddings(openai_api_version="2020-11-07", + openai_api_key=kwargs.get("OPENAI_API_KEY")), + metadatas=metadatas) return store def persist(self): diff --git a/metagpt/llm.py b/metagpt/llm.py deleted file mode 100644 index 6a9a9132f..000000000 --- a/metagpt/llm.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/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/management/skill_manager.py b/metagpt/management/skill_manager.py index f067e6df6..4f141832a 100644 --- a/metagpt/management/skill_manager.py +++ b/metagpt/management/skill_manager.py @@ -4,11 +4,11 @@ @Time : 2023/6/5 01:44 @Author : alexanderwu @File : skill_manager.py +@Modified By: mashenquan, 2023/8/20. Remove useless `_llm` """ from metagpt.actions import Action from metagpt.const import PROMPT_PATH from metagpt.document_store.chromadb_store import ChromaStore -from metagpt.llm import LLM from metagpt.logs import logger Skill = Action @@ -18,7 +18,6 @@ class SkillManager: """用来管理所有技能""" def __init__(self): - self._llm = LLM() self._store = ChromaStore('skill_manager') self._skills: dict[str: Skill] = {} diff --git a/metagpt/manager.py b/metagpt/manager.py index 9d238c621..c4565808e 100644 --- a/metagpt/manager.py +++ b/metagpt/manager.py @@ -4,14 +4,15 @@ @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: LLM = LLM()): + def __init__(self, llm): self.llm = llm # Large Language Model self.role_directions = { "BOSS": "Product Manager", diff --git a/metagpt/memory/longterm_memory.py b/metagpt/memory/longterm_memory.py index 3c2963613..041d335ac 100644 --- a/metagpt/memory/longterm_memory.py +++ b/metagpt/memory/longterm_memory.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# @Desc : the implement of Long-term memory +""" +@Desc : the implement of Long-term memory +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" from metagpt.logs import logger from metagpt.memory import Memory @@ -34,13 +37,13 @@ class LongTermMemory(Memory): self.add_batch(messages) self.msg_from_recover = False - def add(self, message: Message): + def add(self, message: Message, **kwargs): super(LongTermMemory, self).add(message) for action in self.rc.watch: if message.cause_by == action and not self.msg_from_recover: # currently, only add role's watching messages to its memory_storage # and ignore adding messages from recover repeatedly - self.memory_storage.add(message) + self.memory_storage.add(message, **kwargs) def remember(self, observed: list[Message], k=0) -> list[Message]: """ diff --git a/metagpt/memory/memory_storage.py b/metagpt/memory/memory_storage.py index 5421e9e65..09cd67410 100644 --- a/metagpt/memory/memory_storage.py +++ b/metagpt/memory/memory_storage.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# @Desc : the implement of memory storage +""" +@Desc : the implement of memory storage +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" from typing import List from pathlib import Path @@ -61,13 +64,13 @@ class MemoryStorage(FaissStore): super(MemoryStorage, self).persist() logger.debug(f'Agent {self.role_id} persist memory into local') - def add(self, message: Message) -> bool: + def add(self, message: Message, **kwargs) -> bool: """ add message into memory storage""" docs = [message.content] metadatas = [{"message_ser": serialize_message(message)}] if not self.store: # init Faiss - self.store = self._write(docs, metadatas) + self.store = self._write(docs, metadatas, **kwargs) self._initialized = True else: self.store.add_texts(texts=docs, metadatas=metadatas) diff --git a/metagpt/provider/anthropic_api.py b/metagpt/provider/anthropic_api.py index 03802a716..326d23a5c 100644 --- a/metagpt/provider/anthropic_api.py +++ b/metagpt/provider/anthropic_api.py @@ -4,17 +4,22 @@ @Time : 2023/7/21 11:15 @Author : Leo Xiao @File : anthropic_api.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. """ import anthropic from anthropic import Anthropic -from metagpt.config import CONFIG +from metagpt.config import Config class Claude2: + def __init__(self, options=None): + self.options = options or Config().runtime_options + def ask(self, prompt): - client = Anthropic(api_key=CONFIG.claude_api_key) + client = Anthropic(api_key=self.claude_api_key) res = client.completions.create( model="claude-2", @@ -24,7 +29,7 @@ class Claude2: return res.completion async def aask(self, prompt): - client = Anthropic(api_key=CONFIG.claude_api_key) + client = Anthropic(api_key=self.claude_api_key) res = client.completions.create( model="claude-2", @@ -32,3 +37,7 @@ class Claude2: max_tokens_to_sample=1000, ) return res.completion + + @property + def claude_api_key(self): + return self.options.get("claude_api_key") diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 79121c8de..2e951b36f 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -3,6 +3,8 @@ @Time : 2023/5/5 23:08 @Author : alexanderwu @File : openai.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. """ import asyncio import time @@ -12,10 +14,8 @@ import openai from openai.error import APIConnectionError from tenacity import retry, stop_after_attempt, after_log, wait_fixed, retry_if_exception_type -from metagpt.config import CONFIG from metagpt.logs import logger from metagpt.provider.base_gpt_api import BaseGPTAPI -from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( TOKEN_COSTS, count_message_tokens, @@ -56,13 +56,13 @@ class Costs(NamedTuple): total_budget: float -class CostManager(metaclass=Singleton): +class CostManager: """计算使用接口的开销""" - def __init__(self): + def __init__(self, options): self.total_prompt_tokens = 0 self.total_completion_tokens = 0 - self.total_cost = 0 + self.options = options self.total_budget = 0 def update_cost(self, prompt_tokens, completion_tokens, model): @@ -79,10 +79,9 @@ class CostManager(metaclass=Singleton): cost = (prompt_tokens * TOKEN_COSTS[model]["prompt"] + completion_tokens * TOKEN_COSTS[model]["completion"]) / 1000 self.total_cost += cost logger.info( - f"Total running cost: ${self.total_cost:.3f} | Max budget: ${CONFIG.max_budget:.3f} | " + f"Total running cost: ${self.total_cost:.3f} | Max budget: ${self.max_budget:.3f} | " f"Current cost: ${cost:.3f}, prompt_tokens: {prompt_tokens}, completion_tokens: {completion_tokens}" ) - CONFIG.total_cost = self.total_cost def get_total_prompt_tokens(self): """ @@ -115,6 +114,18 @@ class CostManager(metaclass=Singleton): """获得所有开销""" return Costs(self.total_prompt_tokens, self.total_completion_tokens, self.total_cost, self.total_budget) + @property + def total_cost(self): + return self.options.get("total_cost", 0) + + @total_cost.setter + def total_cost(self, v): + self.options["total_cost"] = v + + @property + def max_budget(self): + return self.options.get("max_budget", 0) + def log_and_reraise(retry_state): logger.error(f"Retry attempts exhausted. Last exception: {retry_state.outcome.exception()}") @@ -130,22 +141,23 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): Check https://platform.openai.com/examples for examples """ - def __init__(self): - self.__init_openai(CONFIG) + def __init__(self, options, cost_manager): + self._options = options + self.__init_openai() self.llm = openai - self.model = CONFIG.openai_api_model + self.model = self.openai_api_model self.auto_max_tokens = False - self._cost_manager = CostManager() + self._cost_manager = cost_manager RateLimiter.__init__(self, rpm=self.rpm) - def __init_openai(self, config): - openai.api_key = config.openai_api_key - if config.openai_api_base: - openai.api_base = config.openai_api_base - if config.openai_api_type: - openai.api_type = config.openai_api_type - openai.api_version = config.openai_api_version - self.rpm = int(config.get("RPM", 10)) + def __init_openai(self): + openai.api_key = self.openai_api_key + if self.openai_api_base: + openai.api_base = self.openai_api_base + if self.openai_api_type: + openai.api_type = self.openai_api_type + openai.api_version = self.openai_api_version + self.rpm = int(self._options.get("RPM", 10)) async def _achat_completion_stream(self, messages: list[dict]) -> str: response = await openai.ChatCompletion.acreate(**self._cons_kwargs(messages), stream=True) @@ -168,9 +180,9 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): return full_reply_content def _cons_kwargs(self, messages: list[dict]) -> dict: - if CONFIG.openai_api_type == "azure": + if self._options.get("openai_api_type") == "azure": kwargs = { - "deployment_id": CONFIG.deployment_id, + "deployment_id": self._options.get("deployment_id"), "messages": messages, "max_tokens": self.get_max_tokens(messages), "n": 1, @@ -225,7 +237,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): def _calc_usage(self, messages: list[dict], rsp: str) -> dict: usage = {} - if CONFIG.calc_usage: + if self._options.get("calc_usage"): try: prompt_tokens = count_message_tokens(messages, self.model) completion_tokens = count_string_tokens(rsp, self.model) @@ -264,7 +276,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): return results def _update_costs(self, usage: dict): - if CONFIG.calc_usage: + if self._options.get("calc_usage"): try: prompt_tokens = int(usage['prompt_tokens']) completion_tokens = int(usage['completion_tokens']) @@ -277,5 +289,25 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter): def get_max_tokens(self, messages: list[dict]): if not self.auto_max_tokens: - return CONFIG.max_tokens_rsp - return get_max_completion_tokens(messages, self.model, CONFIG.max_tokens_rsp) + return self._options.get("max_tokens_rsp") + return get_max_completion_tokens(messages, self.model, self._options.get("max_tokens_rsp")) + + @property + def openai_api_model(self): + return self._options.get("openai_api_model") + + @property + def openai_api_key(self): + return self._options.get("openai_api_key") + + @property + def openai_api_base(self): + return self._options.get("openai_api_base") + + @property + def openai_api_type(self): + return self._options.get("openai_api_type") + + @property + def openai_api_version(self): + return self._options.get("openai_api_version") diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index 00b6cb2eb..5a498c50b 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -4,6 +4,8 @@ @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 @@ -12,8 +14,8 @@ from metagpt.roles import Role class Architect(Role): """Architect: Listen to PRD, responsible for designing API, designing code files""" - def __init__(self, name="Bob", profile="Architect", goal="Design a concise, usable, complete python system", + def __init__(self, options, cost_manager, 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, profile, goal, constraints) + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) self._init_actions([WriteDesign]) self._watch({WritePRD}) diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 072e53998..9da2b5a09 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, name="Alex", profile="Engineer", goal="Write elegant, readable, extensible, efficient code", + def __init__(self, options, cost_manager, 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, profile, goal, constraints) + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) 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().run( + code = await WriteCode(options=self.options, llm=self._llm).run( context=self._rc.history, filename=todo ) diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index b42e9bb29..bb69c8dfd 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -4,14 +4,16 @@ @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, name="Alice", profile="Product Manager", goal="Efficiently create a successful product", + def __init__(self, options, cost_manager, name="Alice", profile="Product Manager", goal="Efficiently create a successful product", constraints=""): - super().__init__(name, profile, goal, constraints) + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) self._init_actions([WritePRD]) self._watch([BossRequirement]) diff --git a/metagpt/roles/project_manager.py b/metagpt/roles/project_manager.py index ff374de13..3e8b36550 100644 --- a/metagpt/roles/project_manager.py +++ b/metagpt/roles/project_manager.py @@ -4,14 +4,16 @@ @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, name="Eve", profile="Project Manager", + def __init__(self, options, cost_manager, name="Eve", profile="Project Manager", goal="Improve team efficiency and deliver with quality and quantity", constraints=""): - super().__init__(name, profile, goal, constraints) + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) self._init_actions([WriteTasks]) self._watch([WriteDesign]) diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index 65bf2cc5b..ac5df0dbd 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -20,13 +20,15 @@ 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, profile, goal, constraints) + super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager) 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/role.py b/metagpt/roles/role.py index d3750495f..3c72876a5 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -4,17 +4,16 @@ @Time : 2023/5/11 14:42 @Author : alexanderwu @File : role.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 __future__ import annotations -from typing import Iterable, Type +from typing import Iterable, Type, Dict from pydantic import BaseModel, Field - -# from metagpt.environment import Environment -from metagpt.config import CONFIG +from metagpt.provider.openai_api import OpenAIGPTAPI as LLM from metagpt.actions import Action, ActionOutput -from metagpt.llm import LLM from metagpt.logs import logger from metagpt.memory import Memory, LongTermMemory from metagpt.schema import Message @@ -71,12 +70,13 @@ 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 hasattr(CONFIG, "long_term_memory") and CONFIG.long_term_memory: + if self.options.get("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 @@ -93,13 +93,15 @@ class RoleContext(BaseModel): class Role: """角色/代理""" - def __init__(self, name="", profile="", goal="", constraints="", desc=""): - self._llm = LLM() + def __init__(self, options, cost_manager, name="", profile="", goal="", constraints="", desc=""): + self._options = options if options else {} + self._cost_manager = cost_manager + self._llm = LLM(options=self._options, cost_manager=cost_manager) 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() + self._rc = RoleContext(options=options) def _reset(self): self._states = [] @@ -109,7 +111,7 @@ class Role: self._reset() for idx, action in enumerate(actions): if not isinstance(action, Action): - i = action("") + i = action(options=self._options, name="", llm=self._llm) else: i = action i.set_prefix(self._get_prefix(), self.profile) @@ -137,6 +139,14 @@ class Role: """获取角色描述(职位)""" return self._setting.profile + @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: diff --git a/metagpt/software_company.py b/metagpt/software_company.py index 8f173ebf3..3f6f484b4 100644 --- a/metagpt/software_company.py +++ b/metagpt/software_company.py @@ -4,16 +4,21 @@ @Time : 2023/5/12 00:30 @Author : alexanderwu @File : software_company.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 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): @@ -24,6 +29,8 @@ 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 @@ -35,12 +42,12 @@ class SoftwareCompany(BaseModel): def invest(self, investment: float): """Invest company. raise NoMoneyException when exceed max_budget.""" self.investment = investment - CONFIG.max_budget = investment + self.options["max_budget"] = investment logger.info(f'Investment: ${investment}.') def _check_balance(self): - if CONFIG.total_cost > CONFIG.max_budget: - raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}') + if self.total_cost > self.max_budget: + raise NoMoneyException(self.total_cost, f'Insufficient funds: {self.max_budget}') def start_project(self, idea): """Start a project from publishing boss requirement.""" @@ -59,3 +66,13 @@ 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/search_engine.py b/metagpt/tools/search_engine.py index d28700054..c82ae6595 100644 --- a/metagpt/tools/search_engine.py +++ b/metagpt/tools/search_engine.py @@ -4,13 +4,13 @@ @Time : 2023/5/6 20:15 @Author : alexanderwu @File : search_engine.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from __future__ import annotations import importlib -from typing import Callable, Coroutine, Literal, overload +from typing import Callable, Coroutine, Literal, overload, Dict -from metagpt.config import CONFIG from metagpt.tools import SearchEngineType @@ -25,24 +25,26 @@ class SearchEngine: run_func: The function to run the search. engine: The search engine type. """ + def __init__( - self, - engine: SearchEngineType | None = None, - run_func: Callable[[str, int, bool], Coroutine[None, None, str | list[str]]] = None, + self, + options: Dict, + engine: SearchEngineType | None = None, + run_func: Callable[[str, int, bool], Coroutine[None, None, str | list[str]]] = None ): - engine = engine or CONFIG.search_engine + engine = engine or options.get("search_engine") if engine == SearchEngineType.SERPAPI_GOOGLE: module = "metagpt.tools.search_engine_serpapi" - run_func = importlib.import_module(module).SerpAPIWrapper().run + run_func = importlib.import_module(module).SerpAPIWrapper(**options).run elif engine == SearchEngineType.SERPER_GOOGLE: module = "metagpt.tools.search_engine_serper" - run_func = importlib.import_module(module).SerperWrapper().run + run_func = importlib.import_module(module).SerperWrapper(**options).run elif engine == SearchEngineType.DIRECT_GOOGLE: module = "metagpt.tools.search_engine_googleapi" - run_func = importlib.import_module(module).GoogleAPIWrapper().run + run_func = importlib.import_module(module).GoogleAPIWrapper(**options).run elif engine == SearchEngineType.DUCK_DUCK_GO: module = "metagpt.tools.search_engine_ddg" - run_func = importlib.import_module(module).DDGAPIWrapper().run + run_func = importlib.import_module(module).DDGAPIWrapper(**options).run elif engine == SearchEngineType.CUSTOM_ENGINE: pass # run_func = run_func else: @@ -52,19 +54,19 @@ class SearchEngine: @overload def run( - self, - query: str, - max_results: int = 8, - as_string: Literal[True] = True, + self, + query: str, + max_results: int = 8, + as_string: Literal[True] = True, ) -> str: ... @overload def run( - self, - query: str, - max_results: int = 8, - as_string: Literal[False] = False, + self, + query: str, + max_results: int = 8, + as_string: Literal[False] = False, ) -> list[dict[str, str]]: ... diff --git a/metagpt/tools/search_engine_ddg.py b/metagpt/tools/search_engine_ddg.py index 57bc61b82..78562c77e 100644 --- a/metagpt/tools/search_engine_ddg.py +++ b/metagpt/tools/search_engine_ddg.py @@ -1,11 +1,14 @@ #!/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 import json from concurrent import futures -from typing import Literal, overload +from typing import Literal, overload, Optional try: from duckduckgo_search import DDGS @@ -15,8 +18,6 @@ except ImportError: "You can install it by running the command: `pip install -e.[search-ddg]`" ) -from metagpt.config import CONFIG - class DDGAPIWrapper: """Wrapper around duckduckgo_search API. @@ -25,43 +26,44 @@ class DDGAPIWrapper: """ def __init__( - self, - *, - loop: asyncio.AbstractEventLoop | None = None, - executor: futures.Executor | None = None, + self, + *, + global_proxy: Optional[str] = None, + loop: asyncio.AbstractEventLoop | None = None, + executor: futures.Executor | None = None, ): kwargs = {} - if CONFIG.global_proxy: - kwargs["proxies"] = CONFIG.global_proxy + if global_proxy: + kwargs["proxies"] = global_proxy self.loop = loop self.executor = executor self.ddgs = DDGS(**kwargs) @overload def run( - self, - query: str, - max_results: int = 8, - as_string: Literal[True] = True, - focus: list[str] | None = None, + self, + query: str, + max_results: int = 8, + as_string: Literal[True] = True, + focus: list[str] | None = None, ) -> str: ... @overload def run( - self, - query: str, - max_results: int = 8, - as_string: Literal[False] = False, - focus: list[str] | None = None, + self, + query: str, + max_results: int = 8, + as_string: Literal[False] = False, + focus: list[str] | None = None, ) -> list[dict[str, str]]: ... async def run( - self, - query: str, - max_results: int = 8, - as_string: bool = True, + self, + query: str, + max_results: int = 8, + as_string: bool = True, ) -> str | list[dict]: """Return the results of a Google search using the official Google API diff --git a/metagpt/tools/search_engine_googleapi.py b/metagpt/tools/search_engine_googleapi.py index b9faf2ced..b5aeb5875 100644 --- a/metagpt/tools/search_engine_googleapi.py +++ b/metagpt/tools/search_engine_googleapi.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" from __future__ import annotations import asyncio @@ -11,7 +14,6 @@ from urllib.parse import urlparse import httplib2 from pydantic import BaseModel, validator -from metagpt.config import CONFIG from metagpt.logs import logger try: @@ -27,6 +29,7 @@ except ImportError: class GoogleAPIWrapper(BaseModel): google_api_key: Optional[str] = None google_cse_id: Optional[str] = None + global_proxy: Optional[str] = None loop: Optional[asyncio.AbstractEventLoop] = None executor: Optional[futures.Executor] = None @@ -36,7 +39,6 @@ class GoogleAPIWrapper(BaseModel): @validator("google_api_key", always=True) @classmethod def check_google_api_key(cls, val: str): - val = val or CONFIG.google_api_key if not val: raise ValueError( "To use, make sure you provide the google_api_key when constructing an object. Alternatively, " @@ -47,8 +49,7 @@ class GoogleAPIWrapper(BaseModel): @validator("google_cse_id", always=True) @classmethod - def check_google_cse_id(cls, val: str): - val = val or CONFIG.google_cse_id + def check_google_cse_id(cls, val): if not val: raise ValueError( "To use, make sure you provide the google_cse_id when constructing an object. Alternatively, " @@ -60,8 +61,8 @@ class GoogleAPIWrapper(BaseModel): @property def google_api_client(self): build_kwargs = {"developerKey": self.google_api_key} - if CONFIG.global_proxy: - parse_result = urlparse(CONFIG.global_proxy) + if self.global_proxy: + parse_result = urlparse(self.global_proxy) proxy_type = parse_result.scheme if proxy_type == "https": proxy_type = "http" diff --git a/metagpt/tools/search_engine_serpapi.py b/metagpt/tools/search_engine_serpapi.py index 750184198..1b93a91e9 100644 --- a/metagpt/tools/search_engine_serpapi.py +++ b/metagpt/tools/search_engine_serpapi.py @@ -4,13 +4,14 @@ @Time : 2023/5/23 18:27 @Author : alexanderwu @File : search_engine_serpapi.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from typing import Any, Dict, Optional, Tuple import aiohttp from pydantic import BaseModel, Field, validator -from metagpt.config import CONFIG +from metagpt.config import Config class SerpAPIWrapper(BaseModel): @@ -32,7 +33,6 @@ class SerpAPIWrapper(BaseModel): @validator("serpapi_api_key", always=True) @classmethod def check_serpapi_api_key(cls, val: str): - val = val or CONFIG.serpapi_api_key if not val: raise ValueError( "To use, make sure you provide the serpapi_api_key when constructing an object. Alternatively, " @@ -112,4 +112,4 @@ class SerpAPIWrapper(BaseModel): if __name__ == "__main__": import fire - fire.Fire(SerpAPIWrapper().run) + fire.Fire(SerpAPIWrapper(Config().runtime_options).run) diff --git a/metagpt/tools/search_engine_serper.py b/metagpt/tools/search_engine_serper.py index 0eec2694b..849839f05 100644 --- a/metagpt/tools/search_engine_serper.py +++ b/metagpt/tools/search_engine_serper.py @@ -4,6 +4,7 @@ @Time : 2023/5/23 18:27 @Author : alexanderwu @File : search_engine_serpapi.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import json from typing import Any, Dict, Optional, Tuple @@ -11,8 +12,6 @@ from typing import Any, Dict, Optional, Tuple import aiohttp from pydantic import BaseModel, Field, validator -from metagpt.config import CONFIG - class SerperWrapper(BaseModel): search_engine: Any #: :meta private: @@ -26,7 +25,6 @@ class SerperWrapper(BaseModel): @validator("serper_api_key", always=True) @classmethod def check_serper_api_key(cls, val: str): - val = val or CONFIG.serper_api_key if not val: raise ValueError( "To use, make sure you provide the serper_api_key when constructing an object. Alternatively, " diff --git a/metagpt/tools/web_browser_engine.py b/metagpt/tools/web_browser_engine.py index 453d87f31..da208dbc9 100644 --- a/metagpt/tools/web_browser_engine.py +++ b/metagpt/tools/web_browser_engine.py @@ -1,29 +1,33 @@ #!/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 importlib -from typing import Any, Callable, Coroutine, Literal, overload +from typing import Any, Callable, Coroutine, Literal, overload, Dict -from metagpt.config import CONFIG +from metagpt.config import Config from metagpt.tools import WebBrowserEngineType from metagpt.utils.parse_html import WebPage class WebBrowserEngine: def __init__( - self, - engine: WebBrowserEngineType | None = None, - run_func: Callable[..., Coroutine[Any, Any, WebPage | list[WebPage]]] | None = None, + self, + options: Dict, + engine: WebBrowserEngineType | None = None, + run_func: Callable[..., Coroutine[Any, Any, WebPage | list[WebPage]]] | None = None, ): - engine = engine or CONFIG.web_browser_engine + engine = engine or options.get("web_browser_engine") if engine == WebBrowserEngineType.PLAYWRIGHT: module = "metagpt.tools.web_browser_engine_playwright" - run_func = importlib.import_module(module).PlaywrightWrapper().run + run_func = importlib.import_module(module).PlaywrightWrapper(options=options).run elif engine == WebBrowserEngineType.SELENIUM: module = "metagpt.tools.web_browser_engine_selenium" - run_func = importlib.import_module(module).SeleniumWrapper().run + run_func = importlib.import_module(module).SeleniumWrapper(options=options).run elif engine == WebBrowserEngineType.CUSTOM: run_func = run_func else: @@ -47,6 +51,10 @@ if __name__ == "__main__": import fire async def main(url: str, *urls: str, engine_type: Literal["playwright", "selenium"] = "playwright", **kwargs): - return await WebBrowserEngine(WebBrowserEngineType(engine_type), **kwargs).run(url, *urls) + conf = Config() + return await WebBrowserEngine(options=conf.runtime_options, + engine=WebBrowserEngineType(engine_type), + **kwargs).run(url, *urls) + fire.Fire(main) diff --git a/metagpt/tools/web_browser_engine_playwright.py b/metagpt/tools/web_browser_engine_playwright.py index 030e7701b..199f8a0d1 100644 --- a/metagpt/tools/web_browser_engine_playwright.py +++ b/metagpt/tools/web_browser_engine_playwright.py @@ -1,14 +1,18 @@ #!/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 import sys from pathlib import Path -from typing import Literal +from typing import Literal, Dict from playwright.async_api import async_playwright -from metagpt.config import CONFIG +from metagpt.config import Config from metagpt.logs import logger from metagpt.utils.parse_html import WebPage @@ -24,18 +28,20 @@ class PlaywrightWrapper: def __init__( self, + options: Dict, browser_type: Literal["chromium", "firefox", "webkit"] | None = None, launch_kwargs: dict | None = None, **kwargs, ) -> None: + self.options = options if browser_type is None: - browser_type = CONFIG.playwright_browser_type + browser_type = options.get("playwright_browser_type") self.browser_type = browser_type launch_kwargs = launch_kwargs or {} - if CONFIG.global_proxy and "proxy" not in launch_kwargs: + if options.get("global_proxy") and "proxy" not in launch_kwargs: args = launch_kwargs.get("args", []) if not any(str.startswith(i, "--proxy-server=") for i in args): - launch_kwargs["proxy"] = {"server": CONFIG.global_proxy} + launch_kwargs["proxy"] = {"server": options.get("global_proxy")} self.launch_kwargs = launch_kwargs context_kwargs = {} if "ignore_https_errors" in kwargs: @@ -75,8 +81,8 @@ class PlaywrightWrapper: executable_path = Path(browser_type.executable_path) if not executable_path.exists() and "executable_path" not in self.launch_kwargs: kwargs = {} - if CONFIG.global_proxy: - kwargs["env"] = {"ALL_PROXY": CONFIG.global_proxy} + if self.options.get("global_proxy"): + kwargs["env"] = {"ALL_PROXY": self.options.get("global_proxy")} await _install_browsers(self.browser_type, **kwargs) if self._has_run_precheck: @@ -144,6 +150,8 @@ if __name__ == "__main__": import fire async def main(url: str, *urls: str, browser_type: str = "chromium", **kwargs): - return await PlaywrightWrapper(browser_type, **kwargs).run(url, *urls) + return await PlaywrightWrapper(options=Config().runtime_options, + browser_type=browser_type, + **kwargs).run(url, *urls) fire.Fire(main) diff --git a/metagpt/tools/web_browser_engine_selenium.py b/metagpt/tools/web_browser_engine_selenium.py index d727709b8..b0fcb3fe1 100644 --- a/metagpt/tools/web_browser_engine_selenium.py +++ b/metagpt/tools/web_browser_engine_selenium.py @@ -1,17 +1,21 @@ #!/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 import importlib from concurrent import futures from copy import deepcopy -from typing import Literal +from typing import Literal, Dict from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait -from metagpt.config import CONFIG +from metagpt.config import Config from metagpt.utils.parse_html import WebPage @@ -29,6 +33,7 @@ class SeleniumWrapper: def __init__( self, + options: Dict, browser_type: Literal["chrome", "firefox", "edge", "ie"] | None = None, launch_kwargs: dict | None = None, *, @@ -36,11 +41,11 @@ class SeleniumWrapper: executor: futures.Executor | None = None, ) -> None: if browser_type is None: - browser_type = CONFIG.selenium_browser_type + browser_type = options.get("selenium_browser_type") self.browser_type = browser_type launch_kwargs = launch_kwargs or {} - if CONFIG.global_proxy and "proxy-server" not in launch_kwargs: - launch_kwargs["proxy-server"] = CONFIG.global_proxy + if options.get("global_proxy") and "proxy-server" not in launch_kwargs: + launch_kwargs["proxy-server"] = options.get("global_proxy") self.executable_path = launch_kwargs.pop("executable_path", None) self.launch_args = [f"--{k}={v}" for k, v in launch_kwargs.items()] @@ -118,6 +123,8 @@ if __name__ == "__main__": import fire async def main(url: str, *urls: str, browser_type: str = "chrome", **kwargs): - return await SeleniumWrapper(browser_type, **kwargs).run(url, *urls) + return await SeleniumWrapper(options=Config().runtime_options, + browser_type=browser_type, + **kwargs).run(url, *urls) fire.Fire(main) diff --git a/metagpt/utils/mermaid.py b/metagpt/utils/mermaid.py index 24aabe8ae..1245671fb 100644 --- a/metagpt/utils/mermaid.py +++ b/metagpt/utils/mermaid.py @@ -4,19 +4,21 @@ @Time : 2023/7/4 10:53 @Author : alexanderwu @File : mermaid.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import subprocess from pathlib import Path -from metagpt.config import CONFIG +from metagpt.config import Config from metagpt.const import PROJECT_ROOT from metagpt.logs import logger from metagpt.utils.common import check_cmd_exists -def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height=2048) -> int: +def mermaid_to_file(options, mermaid_code, output_file_without_suffix, width=2048, height=2048) -> int: """suffix: png/svg/pdf + :param options: runtime context options, created by `Config` class object and changed in flow pipeline :param mermaid_code: mermaid code :param output_file_without_suffix: output filename :param width: @@ -36,12 +38,12 @@ def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height # Call the `mmdc` command to convert the Mermaid code to a PNG logger.info(f"Generating {output_file}..") - if CONFIG.puppeteer_config: + if options.get("puppeteer_config"): subprocess.run( [ - CONFIG.mmdc, + options.get("mmdc"), "-p", - CONFIG.puppeteer_config, + options.get("puppeteer_config"), "-i", str(tmp), "-o", @@ -53,7 +55,7 @@ def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height ] ) else: - subprocess.run([CONFIG.mmdc, "-i", str(tmp), "-o", output_file, "-w", str(width), "-H", str(height)]) + subprocess.run([options.get("mmdc"), "-i", str(tmp), "-o", output_file, "-w", str(width), "-H", str(height)]) return 0 @@ -109,6 +111,8 @@ MMC2 = """sequenceDiagram if __name__ == "__main__": - # logger.info(print_members(print_members)) - mermaid_to_file(MMC1, PROJECT_ROOT / "tmp/1.png") - mermaid_to_file(MMC2, PROJECT_ROOT / "tmp/2.png") + conf = Config() + mermaid_to_file(options=conf.runtime_options, mermaid_code=MMC1, + output_file_without_suffix=PROJECT_ROOT / "tmp/1.png") + mermaid_to_file(options=conf.runtime_options, mermaid_code=MMC2, + output_file_without_suffix=PROJECT_ROOT / "tmp/2.png") diff --git a/startup.py b/startup.py index f37b5286c..116e4073d 100644 --- a/startup.py +++ b/startup.py @@ -1,5 +1,10 @@ #!/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 fire @@ -11,14 +16,15 @@ 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(), - Architect(), - ProjectManager(), - Engineer(n_borg=5, use_code_review=code_review)]) + 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)]) if run_tests: # developing features: run tests on the spot and identify bugs (bug fixing capability comes soon!) - company.hire([QaEngineer()]) + company.hire([QaEngineer(options=company.options, cost_manager=company.cost_manager)]) company.invest(investment) company.start_project(idea) await company.run(n_round=n_round) diff --git a/tests/metagpt/actions/test_write_code.py b/tests/metagpt/actions/test_write_code.py index 7bb18ddf2..04216ad7c 100644 --- a/tests/metagpt/actions/test_write_code.py +++ b/tests/metagpt/actions/test_write_code.py @@ -4,11 +4,13 @@ @Time : 2023/5/11 17:45 @Author : alexanderwu @File : test_write_code.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import pytest +from metagpt.config import Config +from metagpt.provider.openai_api import OpenAIGPTAPI as LLM, CostManager from metagpt.actions.write_code import WriteCode -from metagpt.llm import LLM from metagpt.logs import logger from tests.metagpt.actions.mock import TASKS_2, WRITE_CODE_PROMPT_SAMPLE @@ -16,9 +18,12 @@ from tests.metagpt.actions.mock import TASKS_2, WRITE_CODE_PROMPT_SAMPLE @pytest.mark.asyncio async def test_write_code(): api_design = "设计一个名为'add'的函数,该函数接受两个整数作为输入,并返回它们的和。" - write_code = WriteCode("write_code") + conf = Config() + cost_manager = CostManager(conf.runtime_options) + llm = LLM(options=conf.runtime_options, cost_manager=cost_manager) + write_code = WriteCode(options=conf.runtime_options, name="write_code", llm=llm) - code = await write_code.run(api_design) + code = await write_code.run(api_design, "filename") logger.info(code) # 我们不能精确地预测生成的代码,但我们可以检查某些关键字 @@ -29,6 +34,7 @@ async def test_write_code(): @pytest.mark.asyncio async def test_write_code_directly(): prompt = WRITE_CODE_PROMPT_SAMPLE + '\n' + TASKS_2[0] - llm = LLM() + options = Config().runtime_options + llm = LLM(options=options, cost_manager=CostManager(options=options)) rsp = await llm.aask(prompt) logger.info(rsp) diff --git a/tests/metagpt/memory/test_longterm_memory.py b/tests/metagpt/memory/test_longterm_memory.py index 62a3a2361..457e665fa 100644 --- a/tests/metagpt/memory/test_longterm_memory.py +++ b/tests/metagpt/memory/test_longterm_memory.py @@ -1,8 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# @Desc : unittest of `metagpt/memory/longterm_memory.py` - -from metagpt.config import CONFIG +""" +@Desc : unittest of `metagpt/memory/longterm_memory.py` +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" +from metagpt.config import Config from metagpt.schema import Message from metagpt.actions import BossRequirement from metagpt.roles.role import RoleContext @@ -10,12 +12,13 @@ from metagpt.memory import LongTermMemory def test_ltm_search(): - assert hasattr(CONFIG, "long_term_memory") is True - openai_api_key = CONFIG.openai_api_key + conf = Config() + assert hasattr(conf, "long_term_memory") is True + openai_api_key = conf.openai_api_key assert len(openai_api_key) > 20 role_id = 'UTUserLtm(Product Manager)' - rc = RoleContext(watch=[BossRequirement]) + rc = RoleContext(options=conf.runtime_options, watch=[BossRequirement]) ltm = LongTermMemory() ltm.recover_memory(role_id, rc) @@ -23,19 +26,19 @@ def test_ltm_search(): message = Message(role='BOSS', content=idea, cause_by=BossRequirement) news = ltm.remember([message]) assert len(news) == 1 - ltm.add(message) + ltm.add(message, **conf.runtime_options) sim_idea = 'Write a game of cli snake' sim_message = Message(role='BOSS', content=sim_idea, cause_by=BossRequirement) news = ltm.remember([sim_message]) assert len(news) == 0 - ltm.add(sim_message) + ltm.add(sim_message, **conf.runtime_options) new_idea = 'Write a 2048 web game' new_message = Message(role='BOSS', content=new_idea, cause_by=BossRequirement) news = ltm.remember([new_message]) assert len(news) == 1 - ltm.add(new_message) + ltm.add(new_message, **conf.runtime_options) # restore from local index ltm_new = LongTermMemory() diff --git a/tests/metagpt/test_environment.py b/tests/metagpt/test_environment.py index a0f1f6257..d10c93ec0 100644 --- a/tests/metagpt/test_environment.py +++ b/tests/metagpt/test_environment.py @@ -4,14 +4,17 @@ @Time : 2023/5/12 00:47 @Author : alexanderwu @File : test_environment.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. + """ import pytest from metagpt.actions import BossRequirement +from metagpt.config import Config from metagpt.environment import Environment from metagpt.logs import logger -from metagpt.manager import Manager +from metagpt.provider.openai_api import CostManager from metagpt.roles import Architect, ProductManager, Role from metagpt.schema import Message @@ -22,33 +25,45 @@ def env(): def test_add_role(env: Environment): - role = ProductManager("Alice", "product manager", "create a new product", "limited resources") + conf = Config() + cost_manager = CostManager(options=conf.runtime_options) + role = ProductManager(options=conf.runtime_options, + cost_manager=cost_manager, + name="Alice", + profile="product manager", + goal="create a new product", + constraints="limited resources") env.add_role(role) assert env.get_role(role.profile) == role def test_get_roles(env: Environment): - role1 = Role("Alice", "product manager", "create a new product", "limited resources") - role2 = Role("Bob", "engineer", "develop the new product", "short deadline") + conf = Config() + cost_manager = CostManager(options=conf.runtime_options) + role1 = Role(options=conf.runtime_options, cost_manager=cost_manager, name="Alice", profile="product manager", + goal="create a new product", constraints="limited resources") + role2 = Role(options=conf.runtime_options, cost_manager=cost_manager, name="Bob", profile="engineer", + goal="develop the new product", constraints="short deadline") env.add_role(role1) env.add_role(role2) roles = env.get_roles() assert roles == {role1.profile: role1, role2.profile: role2} -def test_set_manager(env: Environment): - manager = Manager() - env.set_manager(manager) - assert env.manager == manager - - @pytest.mark.asyncio async def test_publish_and_process_message(env: Environment): - product_manager = ProductManager("Alice", "Product Manager", "做AI Native产品", "资源有限") - architect = Architect("Bob", "Architect", "设计一个可用、高效、较低成本的系统,包括数据结构与接口", "资源有限,需要节省成本") + conf = Config() + cost_manager = CostManager(options=conf.runtime_options) + product_manager = ProductManager(options=conf.runtime_options, + cost_manager=cost_manager, + name="Alice", profile="Product Manager", + goal="做AI Native产品", constraints="资源有限") + architect = Architect(options=conf.runtime_options, + cost_manager=cost_manager, + name="Bob", profile="Architect", goal="设计一个可用、高效、较低成本的系统,包括数据结构与接口", + constraints="资源有限,需要节省成本") env.add_roles([product_manager, architect]) - env.set_manager(Manager()) env.publish_message(Message(role="BOSS", content="需要一个基于LLM做总结的搜索引擎", cause_by=BossRequirement)) await env.run(k=2) diff --git a/tests/metagpt/test_llm.py b/tests/metagpt/test_llm.py index 11503af1d..77de6df0c 100644 --- a/tests/metagpt/test_llm.py +++ b/tests/metagpt/test_llm.py @@ -4,16 +4,19 @@ @Time : 2023/5/11 14:45 @Author : alexanderwu @File : test_llm.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ import pytest -from metagpt.llm import LLM +from metagpt.config import Config +from metagpt.provider.openai_api import OpenAIGPTAPI as LLM, CostManager @pytest.fixture() def llm(): - return LLM() + options = Config().runtime_options + return LLM(options=options, cost_manager=CostManager(options)) @pytest.mark.asyncio diff --git a/tests/metagpt/tools/test_search_engine.py b/tests/metagpt/tools/test_search_engine.py index a7fe063a6..35ccdf78b 100644 --- a/tests/metagpt/tools/test_search_engine.py +++ b/tests/metagpt/tools/test_search_engine.py @@ -4,11 +4,13 @@ @Time : 2023/5/2 17:46 @Author : alexanderwu @File : test_search_engine.py +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. """ from __future__ import annotations import pytest +from metagpt.config import Config from metagpt.logs import logger from metagpt.tools import SearchEngineType from metagpt.tools.search_engine import SearchEngine @@ -37,9 +39,10 @@ class MockSearchEnine: ], ) -async def test_search_engine(search_engine_typpe, run_func, max_results, as_string, ): - search_engine = SearchEngine(search_engine_typpe, run_func) - rsp = await search_engine.run("metagpt", max_results=max_results, as_string=as_string) +async def test_search_engine(search_engine_typpe, run_func, max_results, as_string): + conf = Config() + search_engine = SearchEngine(options=conf.runtime_options, engine=search_engine_typpe, run_func=run_func) + rsp = await search_engine.run(query="metagpt", max_results=max_results, as_string=as_string) logger.info(rsp) if as_string: assert isinstance(rsp, str) diff --git a/tests/metagpt/tools/test_web_browser_engine.py b/tests/metagpt/tools/test_web_browser_engine.py index b08d0ca10..283633bd6 100644 --- a/tests/metagpt/tools/test_web_browser_engine.py +++ b/tests/metagpt/tools/test_web_browser_engine.py @@ -1,5 +1,10 @@ +""" +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" + import pytest +from metagpt.config import Config from metagpt.tools import WebBrowserEngineType, web_browser_engine @@ -13,7 +18,8 @@ from metagpt.tools import WebBrowserEngineType, web_browser_engine ids=["playwright", "selenium"], ) async def test_scrape_web_page(browser_type, url, urls): - browser = web_browser_engine.WebBrowserEngine(browser_type) + conf = Config() + browser = web_browser_engine.WebBrowserEngine(options=conf.runtime_options, engine=browser_type) result = await browser.run(url) assert isinstance(result, str) assert "深度赋智" in result diff --git a/tests/metagpt/tools/test_web_browser_engine_playwright.py b/tests/metagpt/tools/test_web_browser_engine_playwright.py index 69e1339e7..add2b2f63 100644 --- a/tests/metagpt/tools/test_web_browser_engine_playwright.py +++ b/tests/metagpt/tools/test_web_browser_engine_playwright.py @@ -1,6 +1,10 @@ +""" +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" + import pytest -from metagpt.config import CONFIG +from metagpt.config import Config from metagpt.tools import web_browser_engine_playwright @@ -15,22 +19,24 @@ from metagpt.tools import web_browser_engine_playwright ids=["chromium-normal", "firefox-normal", "webkit-normal"], ) async def test_scrape_web_page(browser_type, use_proxy, kwagrs, url, urls, proxy, capfd): + conf = Config() + global_proxy = conf.global_proxy try: - global_proxy = CONFIG.global_proxy if use_proxy: - CONFIG.global_proxy = proxy - browser = web_browser_engine_playwright.PlaywrightWrapper(browser_type, **kwagrs) + conf.global_proxy = proxy + browser = web_browser_engine_playwright.PlaywrightWrapper(options=conf.runtime_options, + browser_type=browser_type, **kwagrs) result = await browser.run(url) result = result.inner_text assert isinstance(result, str) - assert "Deepwisdom" in result + assert "DeepWisdom" in result if urls: results = await browser.run(url, *urls) assert isinstance(results, list) assert len(results) == len(urls) + 1 - assert all(("Deepwisdom" in i) for i in results) + assert all(("DeepWisdom" in i) for i in results) if use_proxy: assert "Proxy:" in capfd.readouterr().out finally: - CONFIG.global_proxy = global_proxy + conf.global_proxy = global_proxy diff --git a/tests/metagpt/tools/test_web_browser_engine_selenium.py b/tests/metagpt/tools/test_web_browser_engine_selenium.py index ce322f7bd..278c35c91 100644 --- a/tests/metagpt/tools/test_web_browser_engine_selenium.py +++ b/tests/metagpt/tools/test_web_browser_engine_selenium.py @@ -1,6 +1,10 @@ +""" +@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation. +""" + import pytest -from metagpt.config import CONFIG +from metagpt.config import Config from metagpt.tools import web_browser_engine_selenium @@ -15,11 +19,12 @@ from metagpt.tools import web_browser_engine_selenium ids=["chrome-normal", "firefox-normal", "edge-normal"], ) async def test_scrape_web_page(browser_type, use_proxy, url, urls, proxy, capfd): + conf = Config() + global_proxy = conf.global_proxy try: - global_proxy = CONFIG.global_proxy if use_proxy: - CONFIG.global_proxy = proxy - browser = web_browser_engine_selenium.SeleniumWrapper(browser_type) + conf.global_proxy = proxy + browser = web_browser_engine_selenium.SeleniumWrapper(options=conf.runtime_options, browser_type=browser_type) result = await browser.run(url) result = result.inner_text assert isinstance(result, str) @@ -33,4 +38,4 @@ async def test_scrape_web_page(browser_type, use_proxy, url, urls, proxy, capfd) if use_proxy: assert "Proxy:" in capfd.readouterr().out finally: - CONFIG.global_proxy = global_proxy + conf.global_proxy = global_proxy diff --git a/tests/metagpt/utils/test_config.py b/tests/metagpt/utils/test_config.py index 475bac22b..510892c2f 100644 --- a/tests/metagpt/utils/test_config.py +++ b/tests/metagpt/utils/test_config.py @@ -4,7 +4,7 @@ @Time : 2023/5/1 11:19 @Author : alexanderwu @File : test_config.py -@Modified By: mashenquan, 2013/8/20, add `test_options` +@Modified By: mashenquan, 2013/8/20, Add `test_options`; remove global configuration `CONFIG`, enable configuration support for business isolation. """ from pathlib import Path @@ -13,12 +13,6 @@ import pytest from metagpt.config import Config -def test_config_class_is_singleton(): - config_1 = Config() - config_2 = Config() - assert config_1 == config_2 - - def test_config_class_get_key_exception(): with pytest.raises(Exception) as exc_info: config = Config() @@ -27,16 +21,15 @@ def test_config_class_get_key_exception(): def test_config_yaml_file_not_exists(): - config = Config('wtf.yaml') with pytest.raises(Exception) as exc_info: - config.get('OPENAI_BASE_URL') - assert str(exc_info.value) == "Key 'OPENAI_BASE_URL' not found in environment variables or in the YAML file" + Config(Path('wtf.yaml')) + assert str(exc_info.value) == "Set OPENAI_API_KEY or Anthropic_API_KEY first" def test_options(): filename = Path(__file__).resolve().parent.parent.parent.parent / "config/config.yaml" config = Config(filename) - opts = config.options + opts = config.runtime_options assert opts