feat: replaced with OPTIONS

This commit is contained in:
莘权 马 2023-08-28 17:45:45 +08:00
parent 3a1ebf19b7
commit 143ffb0c2c
39 changed files with 144 additions and 252 deletions

View file

@ -4,7 +4,7 @@
@Time : 2023/5/11 14:43
@Author : alexanderwu
@File : action.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
@Modified By: mashenquan, 2023/8/20. Add function return annotations.
"""
from abc import ABC
from typing import Optional
@ -12,15 +12,16 @@ from typing import Optional
from tenacity import retry, stop_after_attempt, wait_fixed
from metagpt.actions.action_output import ActionOutput
from metagpt.config import Config
from metagpt.llm import LLM
from metagpt.utils.common import OutputParser
from metagpt.logs import logger
class Action(ABC):
def __init__(self, options=None, name: str = '', context=None, llm=None):
self.options = options or Config().runtime_options
def __init__(self, name: str = '', context=None, llm: LLM = None):
self.name: str = name
if llm is None:
llm = LLM()
self.llm = llm
self.context = context
self.prefix = ""

View file

@ -4,6 +4,7 @@
@Time : 2023/7/11 10:03
@Author : chengmaoyu
@File : action_output
@Modified By: mashenquan, 2023/8/20. Allow 'instruct_content' to be blank.
"""
from typing import Dict, Type, Optional

View file

@ -4,7 +4,6 @@
@Time : 2023/5/19 12:01
@Author : alexanderwu
@File : analyze_dep_libs.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions import Action
@ -27,8 +26,8 @@ Focus only on the names of shared dependencies, do not add any other explanation
class AnalyzeDepLibs(Action):
def __init__(self, options, name, context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
self.desc = "根据上下文,分析程序运行依赖库"
async def run(self, requirement, filepaths_string):

View file

@ -4,7 +4,6 @@
@Time : 2023/5/11 17:46
@Author : alexanderwu
@File : debug_error.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
import re
@ -26,8 +25,8 @@ Now you should start rewriting the code:
## file name of the code to rewrite: Write code with triple quoto. Do your best to implement THIS IN ONLY ONE FILE.
"""
class DebugError(Action):
def __init__(self, options, name="DebugError", context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="DebugError", context=None, llm=None):
super().__init__(name, context, llm)
# async def run(self, code, error):
# prompt = f"Here is a piece of Python code:\n\n{code}\n\nThe following error occurred during execution:" \

View file

@ -5,7 +5,6 @@
@Author : alexanderwu
@File : design_api.py
@Modified By: mashenquan, 2023-8-9, align `run` parameters with the parent :class:`Action` class.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
import shutil
from pathlib import Path
@ -92,8 +91,8 @@ OUTPUT_MAPPING = {
class WriteDesign(Action):
def __init__(self, options, name, context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
self.desc = "Based on the PRD, think about the system design, and design the corresponding APIs, " \
"data structures, library tables, processes, and paths. Please provide your design, feedback " \
"clearly and in detail."
@ -108,15 +107,15 @@ class WriteDesign(Action):
def _save_prd(self, docs_path, resources_path, prd):
prd_file = docs_path / 'prd.md'
quadrant_chart = CodeParser.parse_code(block="Competitive Quadrant Chart", text=prd)
mermaid_to_file(options=self.options, mermaid_code=quadrant_chart, output_file_without_suffix=resources_path / 'competitive_analysis')
mermaid_to_file(quadrant_chart, resources_path / 'competitive_analysis')
logger.info(f"Saving PRD to {prd_file}")
prd_file.write_text(prd)
def _save_system_design(self, docs_path, resources_path, content):
data_api_design = CodeParser.parse_code(block="Data structures and interface definitions", text=content)
seq_flow = CodeParser.parse_code(block="Program call flow", text=content)
mermaid_to_file(options=self.options, mermaid_code=data_api_design, output_file_without_suffix=resources_path / 'data_api_design')
mermaid_to_file(options=self.options, mermaid_code=seq_flow, output_file_without_suffix=resources_path / 'seq_flow')
mermaid_to_file(data_api_design, resources_path / 'data_api_design')
mermaid_to_file(seq_flow, resources_path / 'seq_flow')
system_design_file = docs_path / 'system_design.md'
logger.info(f"Saving System Designs to {system_design_file}")
system_design_file.write_text(content)

View file

@ -4,14 +4,13 @@
@Time : 2023/5/11 19:31
@Author : alexanderwu
@File : design_api_review.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions.action import Action
class DesignReview(Action):
def __init__(self, options, name, context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
async def run(self, prd, api_design):
prompt = f"Here is the Product Requirement Document (PRD):\n\n{prd}\n\nHere is the list of APIs designed " \

View file

@ -4,7 +4,6 @@
@Time : 2023/5/19 11:50
@Author : alexanderwu
@File : design_filenames.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions import Action
from metagpt.logs import logger
@ -16,8 +15,8 @@ Do not add any other explanations, just return a Python string list."""
class DesignFilenames(Action):
def __init__(self, options, name, context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
self.desc = "Based on the PRD, consider system design, and carry out the basic design of the corresponding " \
"APIs, data structures, and database tables. Please give your design, feedback clearly and in detail."

View file

@ -5,7 +5,6 @@
@Author : alexanderwu
@File : project_management.py
@Modified By: mashenquan, 2023-8-9, align `run` parameters with the parent :class:`Action` class.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from typing import List, Tuple
@ -105,8 +104,8 @@ OUTPUT_MAPPING = {
class WriteTasks(Action):
def __init__(self, options, name="CreateTasks", context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="CreateTasks", context=None, llm=None):
super().__init__(name, context, llm)
def _save(self, context, rsp):
ws_name = CodeParser.parse_str(block="Python package name", text=context[-1].content)

View file

@ -1,9 +1,5 @@
#!/usr/bin/env python
"""
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from __future__ import annotations
import asyncio
@ -13,6 +9,7 @@ from typing import Callable
from pydantic import parse_obj_as
from metagpt.actions import Action
from metagpt.config import CONFIG
from metagpt.logs import logger
from metagpt.tools.search_engine import SearchEngine
from metagpt.tools.web_browser_engine import WebBrowserEngine, WebBrowserEngineType
@ -82,15 +79,14 @@ class CollectLinks(Action):
"""Action class to collect links from a search engine."""
def __init__(
self,
options,
name: str = "",
*args,
rank_func: Callable[[list[str]], None] | None = None,
**kwargs,
):
super().__init__(options=options, name=name, *args, **kwargs)
super().__init__(name, *args, **kwargs)
self.desc = "Collect links from a search engine."
self.search_engine = SearchEngine(options=options)
self.search_engine = SearchEngine()
self.rank_func = rank_func
async def run(
@ -130,7 +126,7 @@ class CollectLinks(Action):
remove.pop()
if len(remove) == 0:
break
prompt = reduce_message_length(gen_msg(), self.llm.model, system_text, self.options.get("max_tokens_rsp"))
prompt = reduce_message_length(gen_msg(), self.llm.model, system_text, CONFIG.max_tokens_rsp)
logger.debug(prompt)
queries = await self._aask(prompt, [system_text])
try:
@ -182,10 +178,9 @@ class WebBrowseAndSummarize(Action):
**kwargs,
):
super().__init__(*args, **kwargs)
if self.options.get("model_for_researcher_summary"):
self.llm.model = self.options.get("model_for_researcher_summary")
if CONFIG.model_for_researcher_summary:
self.llm.model = CONFIG.model_for_researcher_summary
self.web_browser_engine = WebBrowserEngine(
options=self.options,
engine=WebBrowserEngineType.CUSTOM if browse_func else None,
run_func=browse_func,
)
@ -218,8 +213,7 @@ class WebBrowseAndSummarize(Action):
for u, content in zip([url, *urls], contents):
content = content.inner_text
chunk_summaries = []
for prompt in generate_prompt_chunk(content, prompt_template, self.llm.model, system_text,
self.options.get("max_tokens_rsp")):
for prompt in generate_prompt_chunk(content, prompt_template, self.llm.model, system_text, CONFIG.max_tokens_rsp):
logger.debug(prompt)
summary = await self._aask(prompt, [system_text])
if summary == "Not relevant.":
@ -245,8 +239,8 @@ class ConductResearch(Action):
"""Action class to conduct research and generate a research report."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.options.get("model_for_researcher_report"):
self.llm.model = self.options.get("model_for_researcher_report")
if CONFIG.model_for_researcher_report:
self.llm.model = CONFIG.model_for_researcher_report
async def run(
self,

View file

@ -4,7 +4,6 @@
@Time : 2023/5/11 17:46
@Author : alexanderwu
@File : run_code.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
import os
import subprocess
@ -58,8 +57,8 @@ standard errors: {errs};
class RunCode(Action):
def __init__(self, options, name="RunCode", context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="RunCode", context=None, llm=None):
super().__init__(name, context, llm)
@classmethod
async def run_text(cls, code) -> Tuple[str, str]:

View file

@ -101,16 +101,16 @@ You are a member of a professional butler team and will provide helpful suggesti
class SearchAndSummarize(Action):
def __init__(self, options, name="", context=None, llm=None, engine=None, search_func=None):
self.engine = engine or options.get("search_engine")
def __init__(self, name="", context=None, llm=None, engine=None, search_func=None):
self.engine = engine or CONFIG.search_engine
try:
self.search_engine = SearchEngine(options=options, engine=self.engine, run_func=search_func)
self.search_engine = SearchEngine(self.engine, run_func=search_func)
except pydantic.ValidationError:
self.search_engine = None
self.result = ""
super().__init__(options=options, name=name, context=context, llm=llm)
super().__init__(name, context, llm)
async def run(self, context: list[Message], system_text=SEARCH_AND_SUMMARIZE_SYSTEM) -> str:
if self.search_engine is None:

View file

@ -1,3 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/8/28
@Author : mashenquan
@File : skill_action.py
@Desc : Call learned skill
"""
import ast
import importlib
@ -7,8 +16,8 @@ from metagpt.logs import logger
class ArgumentsParingAction(Action):
def __init__(self, options, last_talk: str, skill: Skill, context=None, llm=None, **kwargs):
super(ArgumentsParingAction, self).__init__(options=options, name='', context=context, llm=llm)
def __init__(self, last_talk: str, skill: Skill, context=None, llm=None, **kwargs):
super(ArgumentsParingAction, self).__init__(name='', context=context, llm=llm)
self.skill = skill
self.ask = last_talk
self.rsp = None
@ -59,15 +68,15 @@ class ArgumentsParingAction(Action):
class SkillAction(Action):
def __init__(self, options, skill: Skill, args: dict, context=None, llm=None, **kwargs):
super(SkillAction, self).__init__(options=options, name='', context=context, llm=llm)
def __init__(self, skill: Skill, args: dict, context=None, llm=None, **kwargs):
super(SkillAction, self).__init__(name='', context=context, llm=llm)
self._skill = skill
self._args = args
self.rsp = None
async def run(self, *args, **kwargs) -> str | ActionOutput | None:
"""Run action"""
self.rsp = self.find_and_call_function(self._skill.name, args=self._args, **self.options)
self.rsp = self.find_and_call_function(self._skill.name, args=self._args, **kwargs)
return ActionOutput(content=self.rsp, instruct_content=self._skill.json())
@staticmethod

View file

@ -4,7 +4,6 @@
@Time : 2023/5/11 17:45
@Author : alexanderwu
@File : write_code.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions import WriteDesign
from metagpt.actions.action import Action
@ -44,8 +43,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
class WriteCode(Action):
def __init__(self, options, name="WriteCode", context: list[Message] = None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="WriteCode", context: list[Message] = None, llm=None):
super().__init__(name, context, llm)
def _is_invalid(self, filename):
return any(i in filename for i in ["mp3", "wav"])

View file

@ -4,7 +4,6 @@
@Time : 2023/5/11 17:45
@Author : alexanderwu
@File : write_code_review.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions.action import Action
@ -63,8 +62,8 @@ FORMAT_EXAMPLE = """
class WriteCodeReview(Action):
def __init__(self, options, name="WriteCodeReview", context: list[Message] = None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="WriteCodeReview", context: list[Message] = None, llm=None):
super().__init__(name, context, llm)
@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
async def write_code(self, prompt):

View file

@ -4,7 +4,6 @@
@Time : 2023/5/11 17:45
@Author : alexanderwu
@File : write_prd.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from typing import List, Tuple
@ -128,11 +127,11 @@ OUTPUT_MAPPING = {
class WritePRD(Action):
def __init__(self, options, name="", context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="", context=None, llm=None):
super().__init__(name, context, llm)
async def run(self, requirements, *args, **kwargs) -> ActionOutput:
sas = SearchAndSummarize(options=self.options, llm=self.llm)
sas = SearchAndSummarize()
# rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US)
rsp = ""
info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"

View file

@ -4,14 +4,13 @@
@Time : 2023/5/11 17:45
@Author : alexanderwu
@File : write_prd_review.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions.action import Action
class WritePRDReview(Action):
def __init__(self, options, name, context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
self.prd = None
self.desc = "Based on the PRD, conduct a PRD Review, providing clear and detailed feedback"
self.prd_review_prompt_template = """

View file

@ -42,7 +42,7 @@ class WriteTeachingPlanPart(Action):
statements = []
from metagpt.roles import Role
for p in statement_patterns:
s = Role.format_value(p, kwargs)
s = Role.format_value(p)
statements.append(s)
formatter = self.PROMPT_TITLE_TEMPLATE if self.topic == self.COURSE_TITLE else self.PROMPT_TEMPLATE
prompt = formatter.format(formation=self.FORMATION,

View file

@ -4,7 +4,6 @@
@Time : 2023/5/11 17:45
@Author : alexanderwu
@File : write_test.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.actions.action import Action
from metagpt.utils.common import CodeParser
@ -31,8 +30,8 @@ you should correctly import the necessary classes based on these file locations!
class WriteTest(Action):
def __init__(self, options, name="WriteTest", context=None, llm=None):
super().__init__(options=options, name=name, context=context, llm=llm)
def __init__(self, name="WriteTest", context=None, llm=None):
super().__init__(name, context, llm)
async def write_code(self, prompt):
code_rsp = await self._aask(prompt)

View file

@ -1,25 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/8/20
@Author : mashenquan
@File : skill_metadata.py
@Desc : Defines metadata for the `skill`.
Depending on the context and specific circumstances, skills may have different effects.
For example:
Proprietor: "Skill of the proprietor entity."
Holder: "Skill of the holder entity."
Possessor: "Skill of the possessor entity."
Controller: "Skill of the controller entity."
Owner: "Skill of the owner entity."
"""
def skill_metadata(name, description, requisite):
def decorator(func):
func.skill_name = name
func.skill_description = description
func.skill_requisite = requisite
return func
return decorator

View file

@ -6,16 +6,11 @@
@File : text_to_embedding.py
@Desc : Text-to-Embedding skill, which provides text-to-embedding functionality.
"""
import os
from metagpt.learn.skill_metadata import skill_metadata
from metagpt.config import CONFIG
from metagpt.tools.openai_text_to_embedding import oas3_openai_text_to_embedding
from metagpt.utils.common import initialize_environment
@skill_metadata(name="Text to Embedding",
description="Convert the text into embeddings.",
requisite="`OPENAI_API_KEY`")
async def text_to_embedding(text, model="text-embedding-ada-002", openai_api_key="", **kwargs):
"""Text to embedding
@ -24,7 +19,6 @@ async def text_to_embedding(text, model="text-embedding-ada-002", openai_api_key
:param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
:return: A json object of :class:`ResultEmbedding` class if successful, otherwise `{}`.
"""
initialize_environment()
if os.environ.get("OPENAI_API_KEY") or openai_api_key:
if CONFIG.OPENAI_API_KEY or openai_api_key:
return await oas3_openai_text_to_embedding(text, model=model, openai_api_key=openai_api_key)
raise EnvironmentError

View file

@ -8,15 +8,11 @@
"""
import os
from metagpt.learn.skill_metadata import skill_metadata
from metagpt.config import CONFIG
from metagpt.tools.metagpt_text_to_image import oas3_metagpt_text_to_image
from metagpt.tools.openai_text_to_image import oas3_openai_text_to_image
from metagpt.utils.common import initialize_environment
@skill_metadata(name="Text to image",
description="Create a drawing based on the text.",
requisite="`OPENAI_API_KEY` or `METAGPT_TEXT_TO_IMAGE_MODEL`")
async def text_to_image(text, size_type: str = "512x512", openai_api_key="", model_url="", **kwargs):
"""Text to image
@ -26,13 +22,12 @@ async def text_to_image(text, size_type: str = "512x512", openai_api_key="", mod
:param model_url: MetaGPT model url
:return: The image data is returned in Base64 encoding.
"""
initialize_environment()
image_declaration = "data:image/png;base64,"
if os.environ.get("METAGPT_TEXT_TO_IMAGE_MODEL_URL") or model_url:
if CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL or model_url:
data = await oas3_metagpt_text_to_image(text, size_type, model_url)
return image_declaration + data if data else ""
if os.environ.get("OPENAI_API_KEY") or openai_api_key:
if CONFIG.OPENAI_API_KEY or openai_api_key:
data = await oas3_openai_text_to_image(text, size_type, openai_api_key)
return image_declaration + data if data else ""

View file

@ -6,16 +6,14 @@
@File : text_to_speech.py
@Desc : Text-to-Speech skill, which provides text-to-speech functionality
"""
import os
from metagpt.learn.skill_metadata import skill_metadata
from metagpt.config import CONFIG
from metagpt.tools.azure_tts import oas3_azsure_tts
from metagpt.utils.common import initialize_environment
@skill_metadata(name="Text to speech",
description="Text-to-speech",
requisite="`AZURE_TTS_SUBSCRIPTION_KEY` and `AZURE_TTS_REGION`")
async def text_to_speech(text, lang="zh-CN", voice="zh-CN-XiaomoNeural", style="affectionate", role="Girl",
subscription_key="", region="", **kwargs):
"""Text to speech
@ -31,9 +29,8 @@ async def text_to_speech(text, lang="zh-CN", voice="zh-CN-XiaomoNeural", style="
:return: Returns the Base64-encoded .wav file data if successful, otherwise an empty string.
"""
initialize_environment()
audio_declaration = "data:audio/wav;base64,"
if (os.environ.get("AZURE_TTS_SUBSCRIPTION_KEY") and os.environ.get("AZURE_TTS_REGION")) or \
if (CONFIG.AZURE_TTS_SUBSCRIPTION_KEY and CONFIG.AZURE_TTS_REGION) or \
(subscription_key and region):
data = await oas3_azsure_tts(text, lang, voice, style, role, subscription_key, region)
return audio_declaration + data if data else data

20
metagpt/llm.py Normal file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/5/11 14:45
@Author : alexanderwu
@File : llm.py
"""
from metagpt.provider.anthropic_api import Claude2 as Claude
from metagpt.provider.openai_api import OpenAIGPTAPI as LLM
DEFAULT_LLM = LLM()
CLAUDE_LLM = Claude()
async def ai_func(prompt):
"""使用LLM进行QA
QA with LLMs
"""
return await DEFAULT_LLM.aask(prompt)

View file

@ -4,15 +4,14 @@
@Time : 2023/5/11 14:42
@Author : alexanderwu
@File : manager.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.schema import Message
class Manager:
def __init__(self, llm):
def __init__(self, llm: LLM = LLM()):
self.llm = llm # Large Language Model
self.role_directions = {
"BOSS": "Product Manager",

View file

@ -4,8 +4,6 @@
@Time : 2023/5/11 14:43
@Author : alexanderwu
@File : architect.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
from metagpt.actions import WriteDesign, WritePRD
@ -14,8 +12,8 @@ from metagpt.roles import Role
class Architect(Role):
"""Architect: Listen to PRD, responsible for designing API, designing code files"""
def __init__(self, options, cost_manager, name="Bob", profile="Architect", goal="Design a concise, usable, complete python system",
def __init__(self, name="Bob", profile="Architect", goal="Design a concise, usable, complete python system",
constraints="Try to specify good open source tools as much as possible"):
super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager)
super().__init__(name, profile, goal, constraints)
self._init_actions([WriteDesign])
self._watch({WritePRD})

View file

@ -26,11 +26,9 @@ DESC = """
class CustomerService(Sales):
def __init__(
self,
options,
cost_manager,
name="Xiaomei",
profile="Human customer service",
desc=DESC,
store=None
):
super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, desc=desc, store=store)
super().__init__(name, profile, desc=desc, store=store)

View file

@ -47,10 +47,10 @@ async def gather_ordered_k(coros, k) -> list:
class Engineer(Role):
def __init__(self, options, cost_manager, name="Alex", profile="Engineer", goal="Write elegant, readable, extensible, efficient code",
def __init__(self, name="Alex", profile="Engineer", goal="Write elegant, readable, extensible, efficient code",
constraints="The code you write should conform to code standard like PEP8, be modular, easy to read and maintain",
n_borg=1, use_code_review=False):
super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager)
super().__init__(name, profile, goal, constraints)
self._init_actions([WriteCode])
self.use_code_review = use_code_review
if self.use_code_review:
@ -131,7 +131,7 @@ class Engineer(Role):
async def _act_sp(self) -> Message:
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
for todo in self.todos:
code = await WriteCode(options=self.options, llm=self._llm).run(
code = await WriteCode().run(
context=self._rc.history,
filename=todo
)

View file

@ -4,16 +4,14 @@
@Time : 2023/5/11 14:43
@Author : alexanderwu
@File : product_manager.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
from metagpt.actions import BossRequirement, WritePRD
from metagpt.roles import Role
class ProductManager(Role):
def __init__(self, options, cost_manager, name="Alice", profile="Product Manager", goal="Efficiently create a successful product",
def __init__(self, name="Alice", profile="Product Manager", goal="Efficiently create a successful product",
constraints=""):
super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager)
super().__init__(name, profile, goal, constraints)
self._init_actions([WritePRD])
self._watch([BossRequirement])

View file

@ -4,16 +4,14 @@
@Time : 2023/5/11 15:04
@Author : alexanderwu
@File : project_manager.py
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
from metagpt.actions import WriteDesign, WriteTasks
from metagpt.roles import Role
class ProjectManager(Role):
def __init__(self, options, cost_manager, name="Eve", profile="Project Manager",
def __init__(self, name="Eve", profile="Project Manager",
goal="Improve team efficiency and deliver with quality and quantity", constraints=""):
super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager)
super().__init__(name, profile, goal, constraints)
self._init_actions([WriteTasks])
self._watch([WriteDesign])

View file

@ -20,15 +20,13 @@ from metagpt.utils.special_tokens import FILENAME_CODE_SEP, MSG_SEP
class QaEngineer(Role):
def __init__(
self,
options,
cost_manager,
name="Edward",
profile="QaEngineer",
goal="Write comprehensive and robust tests to ensure codes will work as expected without bugs",
constraints="The test code you write should conform to code standard like PEP8, be modular, easy to read and maintain",
test_round_allowed=5,
):
super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, options=options, cost_manager=cost_manager)
super().__init__(name, profile, goal, constraints)
self._init_actions(
[WriteTest]
) # FIXME: a bit hack here, only init one action to circumvent _think() logic, will overwrite _think() in future updates

View file

@ -26,8 +26,6 @@ class Report(BaseModel):
class Researcher(Role):
def __init__(
self,
options,
cost_manager,
name: str = "David",
profile: str = "Researcher",
goal: str = "Gather information and conduct research",
@ -35,11 +33,8 @@ class Researcher(Role):
language: str = "en-us",
**kwargs,
):
super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, goal=goal, constraints=constraints, **kwargs)
self._init_actions([
CollectLinks(options=options, name=name),
WebBrowseAndSummarize(options=options, name=name),
ConductResearch(options=options, name=name)])
super().__init__(name, profile, goal, constraints, **kwargs)
self._init_actions([CollectLinks(name), WebBrowseAndSummarize(name), ConductResearch(name)])
self.language = language
if language not in ("en-us", "zh-cn"):
logger.warning(f"The language `{language}` has not been tested, it may not work.")

View file

@ -4,9 +4,7 @@
@Time : 2023/5/11 14:42
@Author : alexanderwu
@File : role.py
@Modified By: mashenquan, 2023-8-7, :class:`Role` + properties.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
@Modified By: mashenquan, 2023-8-7, Support template-style variables, such as '{teaching_language} Teacher'.
@Modified By: mashenquan, 2023/8/22. A definition has been provided for the return value of _think: returning false indicates that further reasoning cannot continue.
"""
from __future__ import annotations
@ -15,7 +13,8 @@ from typing import Iterable, Type, Dict
from pydantic import BaseModel, Field
from metagpt.config import Config
from metagpt.config import Config, CONFIG
from metagpt.const import OPTIONS
from metagpt.provider.openai_api import OpenAIGPTAPI as LLM, CostManager
from metagpt.actions import Action, ActionOutput
from metagpt.logs import logger
@ -74,13 +73,12 @@ class RoleContext(BaseModel):
todo: Action = Field(default=None)
watch: set[Type[Action]] = Field(default_factory=set)
news: list[Type[Message]] = Field(default=[])
options: Dict
class Config:
arbitrary_types_allowed = True
def check(self, role_id: str):
if self.options.get("long_term_memory"):
if CONFIG.long_term_memory:
self.long_term_memory.recover_memory(role_id, self)
self.memory = self.long_term_memory # use memory to act as long_term_memory for unify operation
@ -102,26 +100,20 @@ class RoleContext(BaseModel):
class Role:
"""Role/Proxy"""
def __init__(self, options=None, cost_manager=None, name="", profile="", goal="", constraints="", desc="", *args, **kwargs):
options = options or Config().runtime_options
cost_manager = cost_manager or CostManager(*options)
self._options = Role.supply_options(options=kwargs, default_options=options)
name = Role.format_value(name, self._options)
profile = Role.format_value(profile, self._options)
goal = Role.format_value(goal, self._options)
constraints = Role.format_value(constraints, self._options)
desc = Role.format_value(desc, self._options)
self._cost_manager = cost_manager
self._llm = LLM(options=self._options, cost_manager=cost_manager)
def __init__(self, name="", profile="", goal="", constraints="", desc="", *args, **kwargs):
# Replace template-style variables, such as '{teaching_language} Teacher'.
name = Role.format_value(name)
profile = Role.format_value(profile)
goal = Role.format_value(goal)
constraints = Role.format_value(constraints)
desc = Role.format_value(desc)
self._llm = LLM()
self._setting = RoleSetting(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc)
self._states = []
self._actions = []
self._role_id = str(self._setting)
self._rc = RoleContext(options=self._options)
self._rc = RoleContext()
def _reset(self):
self._states = []
@ -131,7 +123,7 @@ class Role:
self._reset()
for idx, action in enumerate(actions):
if not isinstance(action, Action):
i = action(options=self._options, name="", llm=self._llm)
i = action("", llm=self._llm)
else:
i = action
i.set_prefix(self._get_prefix(), self.profile)
@ -184,14 +176,6 @@ class Role:
"""Return number of action"""
return len(self._actions)
@property
def options(self):
return self._options
@options.setter
def options(self, opts):
self._options.update(opts)
def _get_prefix(self):
"""获取角色前缀"""
if self._setting.desc:
@ -222,7 +206,7 @@ class Role:
logger.info(f"{self._setting}: ready to {self._rc.todo}")
requirement = self._rc.important_memory or self._rc.prerequisite
response = await self._rc.todo.run(requirement, **self._options)
response = await self._rc.todo.run(requirement)
# logger.info(response)
if isinstance(response, ActionOutput):
msg = Message(content=response.content, instruct_content=response.instruct_content,
@ -300,23 +284,14 @@ class Role:
return rsp
@staticmethod
def supply_options(options, default_options=None):
"""Supply missing options"""
ret = default_options.copy() if default_options else {}
if not options:
return ret
ret.update(options)
return ret
@staticmethod
def format_value(value, opts, default_opts=None):
def format_value(value):
"""Fill parameters inside `value` with `options`."""
if not isinstance(value, str):
return value
if "{" not in value:
return value
merged_opts = Role.supply_options(opts, default_opts)
merged_opts = OPTIONS.get() or {}
try:
return value.format(**merged_opts)
except KeyError as e:

View file

@ -13,8 +13,6 @@ from metagpt.tools import SearchEngineType
class Sales(Role):
def __init__(
self,
options,
cost_manager,
name="Xiaomei",
profile="Retail sales guide",
desc="I am a sales guide in retail. My name is Xiaomei. I will answer some customer questions next, and I "
@ -25,7 +23,7 @@ class Sales(Role):
"professional guide",
store=None
):
super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, desc=desc)
super().__init__(name, profile, desc=desc)
self._set_store(store)
def _set_store(self, store):

View file

@ -13,9 +13,9 @@ from metagpt.tools import SearchEngineType
class Searcher(Role):
def __init__(self, options, cost_manager, name='Alice', profile='Smart Assistant', goal='Provide search services for users',
def __init__(self, name='Alice', profile='Smart Assistant', goal='Provide search services for users',
constraints='Answer is rich and complete', engine=SearchEngineType.SERPAPI_GOOGLE, **kwargs):
super().__init__(options=options, cost_manager=cost_manager, name=name, profile=profile, goal=goal, constraints=constraints, **kwargs)
super().__init__(name, profile, goal, constraints, **kwargs)
self._init_actions([SearchAndSummarize(engine=engine)])
def set_search_func(self, search_func):

View file

@ -22,13 +22,13 @@ import re
class Teacher(Role):
"""Support configurable teacher roles,
with native and teaching languages being replaceable through configurations."""
def __init__(self, options, name='Lily', profile='{teaching_language} Teacher',
def __init__(self, name='Lily', profile='{teaching_language} Teacher',
goal='writing a {language} teaching plan part by part',
constraints='writing in {language}', desc="", *args, **kwargs):
super().__init__(options=options, name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs)
super().__init__(name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, *args, **kwargs)
actions = []
for topic in WriteTeachingPlanPart.TOPICS:
act = WriteTeachingPlanPart(options=options, topic=topic, llm=self._llm)
act = WriteTeachingPlanPart(topic=topic, llm=self._llm)
actions.append(act)
self._init_actions(actions)
self._watch({TeachingPlanRequirement})

View file

@ -4,22 +4,16 @@
@Time : 2023/5/12 00:30
@Author : alexanderwu
@File : software_company.py
@Modified By: mashenquan, 2023-07-27, Add `role` & `cause_by` parameters to `start_project()`.
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
from typing import Dict
from pydantic import BaseModel, Field
from metagpt.actions import BossRequirement
from metagpt.config import CONFIG
from metagpt.environment import Environment
from metagpt.logs import logger
from metagpt.provider.openai_api import CostManager
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.utils.common import NoMoneyException
from metagpt.config import Config
class SoftwareCompany(BaseModel):
@ -30,8 +24,6 @@ class SoftwareCompany(BaseModel):
environment: Environment = Field(default_factory=Environment)
investment: float = Field(default=10.0)
idea: str = Field(default="")
options: Dict = Field(default=Config().runtime_options)
cost_manager: CostManager = Field(default=CostManager(**Config().runtime_options))
class Config:
arbitrary_types_allowed = True
@ -43,17 +35,17 @@ class SoftwareCompany(BaseModel):
def invest(self, investment: float):
"""Invest company. raise NoMoneyException when exceed max_budget."""
self.investment = investment
self.options["max_budget"] = investment
CONFIG.max_budget = investment
logger.info(f'Investment: ${investment}.')
def _check_balance(self):
if self.total_cost > self.max_budget:
raise NoMoneyException(self.total_cost, f'Insufficient funds: {self.max_budget}')
if CONFIG.total_cost > CONFIG.max_budget:
raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}')
def start_project(self, idea, role="BOSS", cause_by=BossRequirement):
def start_project(self, idea):
"""Start a project from publishing boss requirement."""
self.idea = idea
self.environment.publish_message(Message(role=role, content=idea, cause_by=cause_by))
self.environment.publish_message(Message(role="BOSS", content=idea, cause_by=BossRequirement))
def _save(self):
logger.info(self.json())
@ -67,13 +59,3 @@ class SoftwareCompany(BaseModel):
self._check_balance()
await self.environment.run()
return self.environment.history
@property
def max_budget(self):
return self.options.get("max_budget", 0)
@property
def total_cost(self):
return self.options.get("total_cost", 0)

View file

@ -17,8 +17,9 @@ import requests
from pydantic import BaseModel
import sys
from metagpt.config import CONFIG
sys.path.append(str(Path(__file__).resolve().parent.parent.parent)) # fix-bug: No module named 'metagpt'
from metagpt.utils.common import initialize_environment
from metagpt.logs import logger
@ -83,12 +84,11 @@ async def oas3_openai_text_to_embedding(text, model="text-embedding-ada-002", op
if not text:
return ""
if not openai_api_key:
openai_api_key = os.environ.get("OPENAI_API_KEY")
openai_api_key = CONFIG.OPENAI_API_KEY
return await OpenAIText2Embedding(openai_api_key).text_2_embedding(text, model=model)
if __name__ == "__main__":
initialize_environment()
loop = asyncio.new_event_loop()
v = loop.create_task(oas3_openai_text_to_embedding("Panda emoji"))
loop.run_until_complete(v)

View file

@ -259,18 +259,3 @@ def parse_recipient(text):
recipient = re.search(pattern, text)
return recipient.group(1) if recipient else ""
def initialize_environment(options=None):
"""Load `config/config.yaml` to `os.environ`"""
if options:
for k, v in options.items():
os.environ[k] = str(v)
return
yaml_file_path = Path(__file__).resolve().parent.parent.parent / "config/config.yaml"
if not yaml_file_path.exists():
return
with open(str(yaml_file_path), "r") as yaml_file:
data = yaml.safe_load(yaml_file)
for k, v in data.items():
os.environ[k] = str(v)

View file

@ -1,10 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
Change cost control from global to company level.
"""
import asyncio
import platform
import fire
@ -16,15 +11,14 @@ from metagpt.software_company import SoftwareCompany
async def startup(idea: str, investment: float = 3.0, n_round: int = 5,
code_review: bool = False, run_tests: bool = False):
"""Run a startup. Be a boss."""
company = SoftwareCompany()
company.hire([ProductManager(options=company.options, cost_manager=company.cost_manager),
Architect(options=company.options, cost_manager=company.cost_manager),
ProjectManager(options=company.options, cost_manager=company.cost_manager),
Engineer(n_borg=5, use_code_review=code_review, options=company.options, cost_manager=company.cost_manager)])
company.hire([ProductManager(),
Architect(),
ProjectManager(),
Engineer(n_borg=5, use_code_review=code_review)])
if run_tests:
# developing features: run tests on the spot and identify bugs (bug fixing capability comes soon!)
company.hire([QaEngineer(options=company.options, cost_manager=company.cost_manager)])
company.hire([QaEngineer()])
company.invest(investment)
company.start_project(idea)
await company.run(n_round=n_round)