mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-05 13:52:38 +02:00
feat: Remove global configuration , enable configuration support for business isolation.
This commit is contained in:
parent
d764b8e6fa
commit
f45a8e5284
50 changed files with 437 additions and 278 deletions
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:" \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 " \
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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 = """
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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] = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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]]:
|
||||
...
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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, "
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
16
startup.py
16
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue